AudioPlayer
Une instance de AudioPlayer peut jouer une audio à partir d'un serveur, charger une audio à partir d'un fichier sur le disque local et la jouer, enregistrer une audio avec un microphone et la jouer, envoyer un fichier audio chargé ou enregistré à un serveur, détruire un fichier audio sur un serveur. Toutes les fonctionnalités sont optionnelles.
RAPPEL : La disposition et le style d'une interface sont à la discrétion du programmeur. Aucun modèle graphique n'est imposé. Coder l'apparence d'une instance de AudioPlayer prend quelques instants. Les exemples sur cette page utilisent les icônes de Font Awesome.
Coordonner un lecteur audio avec une liste de pistes gérées par une instance de AudioPlaylist ne prend que quelques lignes de code.
- Responder
- View
- AudioPlayer
- View
Appuyez sur le bouton lecture pour démarrer la lecture de l'audio. Appuyez sur le bouton pause pour la mettre en pause. Déplacez le glisseur ou cliquez dans la barre de progression pour avancer ou reculer dans l'audio. Activez ou désactivez la lecture en boucle.
Cliquez sur le bouton de chargement pour charger un fichier audio à partir du disque local. Sélectionnez un fichier MP3, OGG ou WAV. NOTE : La taille maximum du fichier est configurée à 10 Mo. Ouvrez un dossier contenant des audios avec l'explorateur de votre système de fichiers. Faites glisser et déposer un fichier MP3, OGG ou WAV sur le lecteur. NOTE : Lire des fichiers AAC et AC3 est aléatoire. Essayez de lire un WEBM et différents conteneurs vidéo.
Cliquez sur le microphone pour commencer l'enregistrement d'une audio. Cliquez sur le microphone ouvert pour arrêter l'enregistrement.
Dès que vous avez chargé ou enregistré une audio, vous pouvez cliquer sur le bouton de téléchargement pour envoyer le fichier au serveur. Le fichier est transféré par blocs de 100 ko. La progression est affichée par un pourcentage. En cas d'erreur, le bouton de téléchargement vire au rouge. NOTE : Sur ce serveur, vous ne pouvez sauvegarder que des fichiers MP3 ou OGG.
Cliquez sur la poubelle pour détruire le fichier sur le serveur. Si le serveur retourne une erreur, la poubelle vire au rouge.
NOTE : Dans cet exemple d'implémentation, l'écriture des données et la destruction du fichier sont simulées par le serveur.
Pour lancer le chargement d'une audio à partir d'un fichier avec un bouton, le code peut exécuter la méthode loadAudio
du lecteur quand le bouton est cliqué.
IMPORTANT : Pour des raisons de sécurité, loadAudio
doit être appelée en réponse à une interaction de l'utilisateur.
- <p class="noprint"><button id="btn_load_audio" type="submit" class="narrow" title=""><i class="fa fa-file-audio"></i></button></p>
- <script>
- document.getElementById('btn_load_audio').onclick = () => audioplayer.loadAudio();
- </script>
Dans la console du navigateur, affichez la durée de l'audio :
audioplayer.duration
Changez l'audio dans le lecteur :
audioplayer.src='/files/sounds/smoke.ogg'
ou
audioplayer.src='/files/sounds/smoke.mp3'
Lancez la lecture de l'audio :
audioplayer.play()
Mettez-la en pause :
audioplayer.pause()
Détruisez l'interface :
audioplayer.destroyWidget()
Redémarrez l'audio :
audioplayer.replay()
Créez et montrez l'interface puis réinitialisez-la :
audioplayer.createManagedWidget().resetWidget()
- function AudioPlayer(options = false) {
- const audio = new Audio();
- if (! (audio.canPlayType('audio/ogg') || audio.canPlayType('audio/mpeg')) )
- throw new TypeError();
- this._audio = audio;
- options = options || {};
- let recorder = options.recorder ? true : false;
- let load = options.load ? true : false;
- let draganddrop = options.draganddrop ? true : false;
- let deleteURL = options.deleteURL;
- let uploadURL = options.uploadURL;
- if (! (typeof deleteURL === 'undefined' || deleteURL === null || typeof deleteURL === 'string'))
- throw new TypeError();
- if (! (typeof uploadURL === 'undefined' || uploadURL === null || typeof uploadURL === 'string'))
- throw new TypeError();
- if (! (recorder || load || draganddrop))
- uploadURL = null;
- if (uploadURL) {
- let chunksize = options.chunksize;
- if (chunksize === undefined)
- chunksize = 100000;
- else if (!Number.isInteger(chunksize))
- throw new TypeError();
- else if (chunksize < 10000)
- throw new RangeError();
- this._chunksize = chunksize;
- let maxfilesize = options.maxfilesize;
- if (maxfilesize === undefined)
- maxfilesize = 1000000;
- else if (!Number.isInteger(maxfilesize))
- throw new TypeError();
- else if (maxfilesize < 100000)
- throw new RangeError();
- this._maxfilesize = maxfilesize;
- }
- View.call(this);
- if (recorder) {
- if (MediaRecorder.isTypeSupported('audio/ogg'))
- this._mediatype = 'audio/ogg';
- else if (MediaRecorder.isTypeSupported('audio/mpeg'))
- this._mediatype = 'audio/mpeg';
- else if (MediaRecorder.isTypeSupported('audio/webm'))
- this._mediatype = 'audio/webm';
- else
- this._mediatype = null;
- recorder = this._mediatype ? true : false;
- }
- this._recorder = recorder;
- this._mediarecorder = null;
- this._mediablob = null;
- this._load = load;
- this._draganddrop = draganddrop;
- this._deleteURL = deleteURL;
- this._uploadURL = uploadURL;
- this._playWidget = null;
- this._pauseWidget = null;
- this._barWidget = null;
- this._timeWidget = null;
- this._loopWidget = null;
- this._loadWidget = null;
- this._fileWidget = null;
- this._deleteWidget = null;
- this._uploadWidget = null;
- this._statusWidget = null;
- this._recordStartWidget = null;
- this._recordStopWidget = null;
- this._seeking = false;
- this._uploading = false;
La classe AudioPlayer hérite de la classe View.
Le paramètre options
du constructeur est un objet qui configure les options recorder
, load
, dragandrop
, deleteURL
, uploadURL
et chunksize
.
Si recorder
vaut true
, l'instance accepte de gérer l'enregistrement d'une audio avec le microphone du navigateur.
Si load
vaut true
, l'instance accepte de charger un fichier audio à partir du disque local en réponse à une interaction de l'utilisateur.
Si draganddrop
vaut true
, l'instance accepte de charger un fichier audio que l'utilisateur a déposé sur l'interface.
deleteURL
est une chaîne de caractères qui spécifie l'URL du POST envoyé au serveur pour détruire une audio.
uploadURL
est une chaîne de caractères qui spécifie l'URL du POST envoyé au serveur pour télécharger une audio.
chunksize
spécifie la taille des blocs de données envoyés au serveur, 100 ko par défaut.
Si le navigateur ne peut pas jouer une audio du type audio/ogg ou audio/mpeg, le constructeur provoque une erreur TypeError.
Si une des options recorder
, load
ou dragandrop
ne vaut pas true
, l'instance ne pourra jamais télécharger une audio et l'option uploadURL
est mise à false
.
Si chunksize
est défini et n'est pas un entier, le constructeur provoque une erreur TypeError. Si chunksize
est un nombre inférieur à 10000, le constructeur provoque une erreur RangeError.
Si recorder
vaut true
, le constructeur vérifie si le navigateur peut enregistrer une audio au format audio/ogg, audio/mpeg ou audio/webm et le mémorise. Sinon, l'option recorder
est mise à false
.
NOTE : L'enregistreur est créé en réponse à la première demande d'enregistrement.
- this._uploadable = false;
- this._autoplay = false;
- this._error = null;
duration
est un accesseur qui retourne la durée en millisecondes de l'audio de this
, 0 par défaut.
- AudioPlayer.prototype = Object.create(View.prototype);
- Object.defineProperty(AudioPlayer.prototype, 'constructor', { value: AudioPlayer, enumerable: false, writable: true });
- Object.defineProperty(AudioPlayer.prototype, 'duration', {
- get: function() {
- return this._audio.src ? Math.floor(this._audio.duration * 1000) : 0;
- }
- });
- Object.defineProperty(AudioPlayer.prototype, 'currentTime', {
- get: function() {
- return this._audio.src ? Math.floor(this._audio.currentTime * 1000) : 0;
currentTime
est un accesseur qui retourne ou change la position en millisecondes de l'audio de this
.
Si this
est en train d'enregistrer ou de télécharger l'audio, changer la position de l'audio est ignoré.
- set: function(ms) {
- if (this._recording || this._uploading)
- return;
- if (!this._audio.src)
- return;
- this._audio.currentTime = ms / 1000;
- }
- });
- Object.defineProperty(AudioPlayer.prototype, 'src', {
- get: function() {
- return this._audio.src;
- },
- set: function(url) {
- if (this._recording || this._uploading)
- return;
- this._autoplay = this.playing && url;
- if (this._audio.src)
- URL.revokeObjectURL(this._audio.src);
- if (url)
- this._audio.src = url;
- else {
- this._audio.removeAttribute('src');
src
est un accesseur qui retourne ou change l'URL de l'audio de this
.
Si this
est en train d'enregistrer ou de télécharger l'audio, changer l'URL de l'audio est ignoré.
- this._showDuration();
- }
- this._mediablob = null;
- this._uploadable = false;
- this._error = null;
- }
- });
- Object.defineProperty(AudioPlayer.prototype, 'loop', {
- get: function() {
- return this._audio.loop;
- },
- this._audio.loop = flag ? true : false;
- if (this._loopWidget) {
- if (this._audio.loop)
- this._loopWidget.classList.remove('off');
- this._loopWidget.classList.add('off');
- }
- }
- });
- get: function() {
- return this._audio.src && !this._audio.paused;
- }
- });
- Object.defineProperty(AudioPlayer.prototype, 'recording', {
- get: function() {
- return this._mediarecorder && this._mediarecorder.state !== 'inactive';
- }
- });
- AudioPlayer.prototype.setFromTracks = function(soundtracks) {
- let url = null;
- if (soundtracks) {
- if ('audio/wav' in soundtracks && this._audio.canPlayType('audio/wav')) {
- url = soundtracks['audio/wav'];
- }
- else if ('audio/ogg' in soundtracks && this._audio.canPlayType('audio/ogg')) {
- url = soundtracks['audio/ogg'];
- else if ('audio/mpeg' in soundtracks && this._audio.canPlayType('audio/mpeg')) {
- url = soundtracks['audio/mpeg'];
- }
- else if ('audio/webm' in soundtracks && this._audio.canPlayType('audio/webm')) {
- url = soundtracks['audio/webm'];
- }
- }
- return this.src = url;
- };
- if (w.tagName != 'AUDIO')
- throw new TypeError();
- for (let source of w.querySelectorAll('source'))
- soundtracks[source.getAttribute('type')] = source.getAttribute('src');
- return this.setFromTracks(soundtracks);
- };
- AudioPlayer.prototype.canPlayType = function(type) {
- return this._audio.canPlayType(type) ? true : false;
- };
- if (!this._audio.src)
- return this;
- if (this._recording)
- return this;
- this._audio.play();
- return this;
- };
- if (!this._audio.src)
- return this;
- if (this._recording)
- return this;
- this._audio.pause();
- return this;
- };
- AudioPlayer.prototype.replay = function() {
- if (!this._audio.src)
- if (this._recording)
- return this;
- this._audio.currentTime = 0;
- this._audio.play();
- return this;
- };
- AudioPlayer.prototype.resetWidget = function() {
- if (this._playWidget && this._pauseWidget) {
- if (!this._audio.src || this._recording) {
- this._playWidget.classList.add('disabled');
- this._pauseWidget.classList.add('disabled');
- }
- else {
- this._playWidget.classList.remove('disabled');
- this._pauseWidget.classList.remove('disabled');
- }
- }
- if (this._barWidget ) {
- if (!this._audio.src || this._recording)
- this._barWidget.disabled = true;
- else
- this._barWidget.disabled = false;
- }
- if (this._loopWidget ) {
- if (this._audio.loop)
- this._loopWidget.classList.remove('off');
- else
- this._loopWidget.classList.add('off');
- }
- if (this._uploadWidget) {
- if (this._uploadable && !this._uploading && !this._recording)
- this._uploadWidget.classList.remove('disabled');
- else
- this._uploadWidget.classList.add('disabled');
- if (this._error == 'upload')
- this._uploadWidget.classList.add('inerror');
- else
- this._uploadWidget.classList.remove('inerror');
- }
- if (this._deleteWidget) {
- if (this._deletable && !this._uploading)
- this._deleteWidget.classList.remove('disabled');
- else
- this._deleteWidget.classList.add('disabled');
- if (this._error == 'delete')
- this._deleteWidget.classList.add('inerror');
- else
- this._deleteWidget.classList.remove('inerror');
- }
- if (this._recordStartWidget && this._recordStopWidget) {
- if (!this._recorder || this._uploading) {
- this._recordStartWidget.classList.add('disabled');
- this._recordStopWidget.classList.add('disabled');
- }
- else {
- this._recordStartWidget.classList.remove('disabled');
- this._recordStopWidget.classList.remove('disabled');
- if (this._error == 'record')
- this._recordStartWidget.classList.add('inerror');
- else
- this._recordStartWidget.classList.remove('inerror');
- }
- }
- if (this._loadWidget) {
- if (this._recording || this._uploading)
- this._loadWidget.classList.add('disabled');
- this._loadWidget.classList.remove('disabled');
- if (this._error == 'load')
- this._loadWidget.classList.add('inerror');
- else
- this._loadWidget.classList.remove('inerror');
- }
- return this;
- };
- AudioPlayer.prototype.setWidget = function(w) {
- View.prototype.setWidget.call(this, w);
- this._playWidget = w.querySelector('.audioplay');
- this._pauseWidget = w.querySelector('.audiopause');
- this._loopWidget = w.querySelector('.audioloop');
- this._timeWidget = w.querySelector('.audiotime');
- this._barWidget = w.querySelector('.audiobar');
- if (this._barWidget && this._barWidget.tagName != 'INPUT')
- this._barWidget = null;
- this._recordStartWidget = w.querySelector('.recordstart');
- this._recordStopWidget = w.querySelector('.recordstop');
- this._loadWidget = w.querySelector('.fileload');
- this._fileWidget = w.querySelector('.mediafile');
- if (this._fileWidget && this._fileWidget.tagName != 'INPUT')
- this._fileWidget = null;
- this._deleteWidget = w.querySelector('.audiodelete');
- this._uploadWidget = w.querySelector('.audioupload');
- this._statusWidget = w.querySelector('.mediastatus');
- const playing = this._audio.src && !this._audio.paused;
- const recording = this._mediarecorder && this._mediarecorder.state !== 'inactive';
- if (this._playWidget) {
- this._playWidget.hidden = playing ? true : false;
- this._playWidget.addEventListener('click', () => {
- if (!this._playWidget.classList.contains('disabled'))
- this.play();
- });
- }
- if (this._pauseWidget) {
- this._pauseWidget.hidden = playing ? false : true;
- this._pauseWidget.addEventListener('click', () => {
- if (!this._pauseWidget.classList.contains('disabled'))
- this.pause();
- });
- }
- if (this._loopWidget) {
- this._loopWidget.addEventListener('click', () => {
- if (!this._loopWidget.classList.contains('disabled'))
- this.loop = !this.loop;
- });
- }
- if (this._barWidget) {
- if (!playing) {
- this._barWidget.value = (this._audio.currentTime ? Math.floor(this._audio.currentTime / this._audio.duration * 100) : 0);
- this._showCurrentTime();
- }
- this._barWidget.addEventListener('mousedown', () => this._seeking = true);
- this._barWidget.addEventListener('mouseup', () => this._seeking = false);
- this._barWidget.addEventListener('change', () => {
- const duration = this._audio.src ? this._audio.duration : 0;
- if (duration) {
- let secs = this._barWidget.value / 100 * duration;
- secs = secs < this._audio.currentTime ? Math.floor(secs) : Math.ceil(secs);
- this._audio.currentTime = this._audio.loop && secs >= duration ? 0 : secs;
- }
- });
- }
- if (this._draganddrop) {
- w.addEventListener('drop', (e) => {
- const dt = e.dataTransfer;
- e.preventDefault();
- if (dt.types.indexOf('Files') != -1) {
- this._loadFile(dt.files[0]);
- }
- });
- w.addEventListener('dragenter', (e) => {
- const dt = e.dataTransfer;
- if (dt.types.indexOf('Files') != -1) {
- e.preventDefault();
- }
- });
- w.addEventListener('dragleave', (e) => {
- e.preventDefault();
- });
- w.addEventListener('dragover', (e) => {
- const dt = e.dataTransfer;
- e.preventDefault();
- dt.dropEffect = dt.types.indexOf('Files') != -1 && !(this._recording || this._uploading) ? 'copy' : 'none';
- });
- }
- if (this._fileWidget)
- this._fileWidget.hidden = true;
- if (this._loadWidget) {
- this._loadWidget.classList.add('disabled');
- if (this._fileWidget) {
- if (this._load) {
- this._loadWidget.addEventListener('click', () => {
- if (!this._loadWidget.classList.contains('disabled') && !(this._recording || this._uploading))
- this._fileWidget.click();
- });
- this._fileWidget.addEventListener('change', (e) => {
- if (e.target.value) {
- this._loadFile(e.target.files[0]);
- }
- });
- }
- }
- else
- this._loadWidget = null;
- }
- if (this._recordStartWidget) {
- this._recordStartWidget.classList.add('disabled');
- this._recordStartWidget.hidden = recording ? true : false;
- if (this._recorder) {
- this._recordStartWidget.addEventListener('click', () => {
- if (!this._recordStartWidget.classList.contains('disabled'))
- this.recordStart();
- });
- }
- else
- this._recordStartWidget = null;
- }
- if (this._recordStopWidget) {
- this._recordStopWidget.hidden = recording ? false : true;
- if (this._recorder) {
- this._recordStopWidget.addEventListener('click', () => {
- if (!this._recordStopWidget.classList.contains('disabled'))
- this.recordStop();
- });
- }
- else
- this._recordStopWidget = null;
- }
- if (this._uploadWidget) {
- this._uploadWidget.classList.add('disabled');
- if (this._uploadURL) {
- this._uploadWidget.addEventListener('click', () => {
- if (!this._uploadWidget.classList.contains('disabled'))
- this.uploadFile();
- });
- }
- else
- this._uploadWidget = null;
- }
- if (this._deleteWidget) {
- this._deleteWidget.classList.add('disabled');
- if (this._deleteURL) {
- this._deleteWidget.addEventListener('click', () => {
- if (!this._deleteWidget.classList.contains('disabled'))
- this.deleteFile();
- });
- }
- else
- this._deleteWidget = null;
- }
- this._audio.onloadedmetadata = () => {
- if (this.interfaced())
- this.resetWidget();
- this._showDuration();
- if (this._autoplay)
- this._audio.play();
- };
- this._audio.ontimeupdate = () => {
- if (this._barWidget && !this._seeking)
- this._barWidget.value = (this._audio.currentTime ? Math.floor(this._audio.currentTime / this._audio.duration * 100) : 0);
- this._showCurrentTime();
- };
- this._audio.onplay = () => {
- if (this._playWidget && this._pauseWidget) {
- this._playWidget.hidden = true;
- this._pauseWidget.hidden = false;
- }
- this.notify('audioPlayed', this);
- };
- this._audio.onpause = () => {
- if (this._playWidget && this._pauseWidget) {
- this._pauseWidget.hidden = true;
- this._playWidget.hidden = false;
- }
- this.notify('audioPaused', this);
- };
- this._audio.onended = () => {
- if (this._playWidget && this._pauseWidget) {
- this._pauseWidget.hidden = true;
- this._playWidget.hidden = false;
- }
- this.notify('audioEnded', this);
- };
- this._audio.onerror = () => {
- if (this._playWidget && this._pauseWidget) {
- this._pauseWidget.hidden = true;
- this._playWidget.hidden = false;
- }
- this._audio.removeAttribute('src');
- if (this.interfaced())
- this.resetWidget();
- this._showDuration();
- this.notify('audioError', this);
- };
- return this;
- };
- AudioPlayer.prototype.destroyWidget = function() {
- View.prototype.destroyWidget.call(this);
- this._playWidget = null;
- this._pauseWidget = null;
- this._barWidget = null;
- this._timeWidget = null;
- this._loopWidget = null;
- this._loadWidget = null;
- this._deleteWidget = null;
- this._uploadWidget = null;
- this._statusWidget = null;
- this._recordStartWidget = null;
- this._recordStopWidget = null;
- return this;
- };
- AudioPlayer.prototype.recordStart = function() {
- if (!this._recorder)
- return this;
- if (this._recording || this._uploading)
- return this;
- this._audio.pause();
- this._recording = true;
- this._error = null;
- if (this.interfaced())
- this.resetWidget();
- if (this._mediarecorder === null) {
- const options = {mimeType: this._mediatype, audioBitsPerSecond: 128000};
- navigator.mediaDevices.getUserMedia({audio: true}).then((stream) => {
- this._mediarecorder = new MediaRecorder(stream, options);
- this._mediarecorder.ignoreMutedMedia = true;
- let recordedtime = 0;
- let mediachunks = [];
- this._mediarecorder.ondataavailable = (e) => {
- if (this._timeWidget)
- this._timeWidget.innerText = AudioPlayer._toHHMMSS(++recordedtime);
- mediachunks.push(e.data);
- };
- this._mediarecorder.onstart = () => {
- if (this._recordStartWidget && this._recordStopWidget) {
- this._recordStartWidget.hidden = true;
- this._recordStopWidget.hidden = false;
- }
- if (this._barWidget)
- this._barWidget.value = 0;
- if (this._timeWidget)
- this._timeWidget.innerText = AudioPlayer._toHHMMSS(recordedtime = 0);
- this.notify('audioRecordStarted', this);
- };
- this._mediarecorder.onstop = () => {
- if (this._recordStartWidget && this._recordStopWidget) {
- this._recordStartWidget.hidden = false;
- this._recordStopWidget.hidden = true;
- }
- const blob = new Blob(mediachunks.splice(0, mediachunks.length), {type: this._mediatype});
- if (this._audio.src)
- URL.revokeObjectURL(this._audio.src);
- this._audio.src = URL.createObjectURL(blob);
- this._mediablob = blob;
- this._recording = false;
- this._uploadable = true;
- if (this.interfaced())
- this.resetWidget();
- this.notify('audioRecordStopped', this);
- };
- this._mediarecorder.onerror = () => {
- if (this._recordStartWidget && this._recordStopWidget) {
- this._recordStartWidget.addClass('inerror');
- this._recordStartWidget.hidden = false;
- this._recordStopWidget.hidden = true;
- }
- this._recording = false;
- this._error = 'record';
- if (this.interfaced())
- this.resetWidget();
- this.notify('audioRecordError', this);
- };
- this._mediarecorder.start(1000);
- })
- .catch((err) => {
- this._recorder = false;
- if (this.interfaced())
- this.resetWidget();
- });
- }
- else
- return this;
- };
- AudioPlayer.prototype.recordStop = function() {
- if (this._mediarecorder)
- this._mediarecorder.stop();
- return this;
- };
- AudioPlayer.prototype.uploadFile = function() {
- if (!this._uploadURL)
- return this;
- if (!this._mediablob)
- return this;
- if (this._recording || this._uploading)
- return this;
- const mediablob = this._mediablob;
- const uploadurl = this._uploadURL;
- const chunksize = this._chunksize;
- const filesize = mediablob.size;
- const filetype = mediablob.type;
- const filereader = new FileReader();
- filereader.onloadend = (e) => postdata(e.target.result);
- let offset = 0, progress = 0, blob;
- const uploadslice = () => {
- if (this._statusWidget && filesize > chunksize)
- this._statusWidget.innerText = `${progress}%`;
- blob = mediablob.slice(offset, offset + chunksize);
- filereader.readAsDataURL(blob);
- };
- const postdata = (data) => {
- $.post(uploadurl, {file_size: filesize, file_type: filetype, file_offset: offset, file_data: data})
- .done(() => {
- offset += blob.size;
- progress = Math.floor((offset / filesize) * 100);
- if (progress < 100)
- uploadslice();
- else {
- if (this._statusWidget)
- this._statusWidget.innerText = '';
- this._uploading = false;
- this._uploadable = false;
- this._deletable = true;
- this._error = null;
- if (this.interfaced())
- this.resetWidget();
- this.notify('audioUploaded', this);
- }
- })
- .fail(() => {
- if (this._statusWidget)
- this._statusWidget.innerText = '';
- this._uploading = false;
- this._error = 'upload';
- if (this.interfaced())
- this.resetWidget();
- this.notify('audioError', this);
- });
- };
- this._uploading = true;
- if (this.interfaced())
- this.resetWidget();
- uploadslice();
- return this;
- };
- AudioPlayer.prototype.deleteFile = function() {
- if (!this._deleteURL)
- return this;
- if (this._uploading)
- return this;
- const deleteurl = this._deleteURL;
- const deletefile = () => {
- $.post(deleteurl)
- .done(() => {
- this._uploadable = this._mediablob ? true : false;
- this._deletable = false;
- this._error = null;
- if (this.interfaced())
- this.resetWidget();
- this.notify('audioDeleted', this);
- })
- .fail(() => {
- this._error = 'delete';
- this.resetWidget();
- this.notify('audioError', this);
- });
- };
- deletefile();
- return this;
- AudioPlayer.prototype.loadAudio = function() {
- if (this._recording || this._uploading)
- return this;
- if (this._fileWidget)
- this._fileWidget.click();
- return this;
- };
- AudioPlayer.prototype._loadFile = function(fd) {
- if (!this._audio.canPlayType(fd.type)) {
- this._error = 'load';
- this.resetWidget();
- return;
- }
- this._autoplay = this.playing;
- if (this._audio.src)
- URL.revokeObjectURL(this._audio.src);
- this.src = URL.createObjectURL(fd);
- this._mediablob = fd;
- this._uploadable = true;
- this.resetWidget();
- this.notify('audioLoaded', this);
- AudioPlayer.prototype._showDuration = function() {
- if (this._timeWidget)
- this._timeWidget.innerText = AudioPlayer._toHHMMSS(this._audio.src ? this._audio.duration : 0);
- };
- AudioPlayer.prototype._showCurrentTime = function() {
- if (this._timeWidget)
- this._timeWidget.innerText = AudioPlayer._toHHMMSS(this._audio.src ? this._audio.currentTime : 0);
- };
- AudioPlayer._toHHMMSS = function(nsecs) {
- nsecs = Math.floor(nsecs);
Serveur
L'audio est copiée dans l'espace disque du serveur en réponse à un POST par une fonction qui en extrait le type MIME et la taille du fichier, le bloc de données qui est transféré, sa taille et sa position dans le fichier. Un bloc de données est encodé en BASE64.
Dans iZend, le fichier uploadaudio.php s'installe directement dans le dossier actions. Pour activer l'URL /uploadaudio dans un site web, une entrée est ajoutée dans le fichier aliases.inc à la liste des URLs communes à toutes les langues.
- $aliases = array(
- array(
- 'captcha' => 'captcha',
- 'uploadaudio' => 'uploadaudio',
- ),
- 'en' => array(
- ),
- );
Adaptez l'installation de cette fonction à votre environnement de développement.
- define('TMP_DIR', ROOT_DIR . DIRECTORY_SEPARATOR . 'tmp');
- define('AUDIO_MAX_SIZE', 10*1000000);
Définit TMP_DIR
- le dossier dans lequel le fichier de l'audio est sauvegardé - au nom du sous-dossier tmp dans le dossier racine du site et AUDIO_MAX_SIZE
- la taille maximum d'une audio - à 10 Mo.
- function uploadaudio($lang, $arglist=false) {
Définit la fonction uploadaudio
qui est appelée par l'URL /uploadaudio du site.
Les arguments $lang
et $arglist
ne sont pas utilisés.
Le contenu retourné par cette action est indépendant du langage de l'utilisateur.
Aucun argument ou paramètre n'est attendu dans l'URL.
- $maxfilesize=AUDIO_MAX_SIZE;
- $filetypes=array('audio/mpeg', 'audio/mp3', 'audio/ogg', 'video/ogg');
Initialise $maxfilesize
à AUDIO_MAX_SIZE
et $filetypes
à la liste de types MIME supportés.
- $type=$data=false;
- $size=$offset=0;
Initialise les variables des arguments du POST.
- if (isset($_POST['file_size'])) {
- $size=$_POST['file_size'];
- }
- if (isset($_POST['file_type'])) {
- $type=$_POST['file_type'];
- }
- if (isset($_POST['file_offset'])) {
- $offset=$_POST['file_offset'];
- }
- if (isset($_POST['file_data'])) {
- $data=explode(';base64,', $_POST['file_data']);
- $data=is_array($data) && isset($data[1]) ? base64_decode($data[1]) : false;
- }
Extrait les arguments du POST. Le bloc de données encodé en BASE64 est décodé.
- if (($offset = filter_var($offset, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0)))) === false)
- goto badrequest;
- if (($size = filter_var($size, FILTER_VALIDATE_INT, array('options' => array('min_range' => 0, 'max_range' => $maxfilesize)))) === false)
- goto badrequest;
- if (!$type or !in_array($type, $filetypes)) {
- goto badrequest;
- }
Vérifie les arguments du POST.
- if (!$data) {
- goto badrequest;
- }
- $datasize=strlen($data);
- if ($offset + $datasize > $size) {
- goto badrequest;
- }
Vérifie la taille des données envoyées dans le POST.
- goto trashfile;
Sur ce serveur, l'écriture du fichier est simulée. Pour vérifier que l'audio a bien été copiée en l'écoutant, commentez cette instruction et adaptez le reste du code pour enregistrer le fichier dans un dossier accessible en écriture par le serveur.
- $name='sound';
- switch ($type) {
- case 'audio/mpeg':
- case 'audio/mp3':
- $fname = $name . '.mp3';
- break;
- case 'audio/ogg':
- case 'video/ogg':
- $fname = $name . '.ogg';
- break;
- default:
- goto badrequest;
- }
- $file = TMP_DIR . DIRECTORY_SEPARATOR . $fname;
Définit le nom du fichier de l'audio. L'extension du nom du fichier dépend du type MIME de l'audio.
- $fout = @fopen($file, $offset == 0 ? 'wb' : 'cb');
- if ($fout === false) {
- goto internalerror;
- }
Ouvre le fichier de l'audio en mode binaire. Si le POST contient le premier bloc de données, le fichier est créé.
- $r = fseek($fout, $offset);
- if ($r == -1) {
- goto internalerror;
- }
Déplace le pointeur du fichier à la position du bloc de données.
- $r = fwrite($fout, $data);
- if ($r === false) {
- goto internalerror;
- }
Écrit le bloc de données.
- if ($offset + $datasize < $size) {
- return false;
- }
Retourne un simple entête 200 Ok
si plus de blocs de données sont attendus.
- return false;
Retourne un simple entête 200 Ok
si tous les blocs de données ont été sauvegardés.
NOTE : Le code laisse de la place pour effectuer un traitement sur le fichier comme une conversion en différents formats avec FFmpeg.
- trashfile:
- return false;
Retourne un simple entête 200 Ok
sans enregistrer les données.
- badrequest:
- header('HTTP/1.1 400 Bad Request');
- return false;
Retourne un simple entête 400 Bad Request
si un des éléments du POST est invalide.
- internalerror:
- header('HTTP/1.1 500 Internal Error');
- return false;
- }
Retourne un simple entête 500 Internal Error
si l'audio n'a pas pu être correctement sauvegardée.
Pour demander au serveur de détruire une audio, le lecteur envoie un simple POST au serveur sans arguments.
- function donothing($lang, $arglist=false) {
- return false;
- }
Définit la fonction donothing
qui est appelée par l'URL /deleteaudio du site.
donothing
ne fait rien et retourne toujours un simple entête 200 Ok
, i. e. détruire un fichier audio est simulé.
Test
- <?php $debug=true; ?>
Mettre $debug
à true
permet d'accéder dans la console du navigateur à la variable audioplayer
, l'instance de AudioPlayer.
Si $debug
vaut false
, tout le code en JavaScript est protégé par une fonction de fermeture.
- <?php $upload_url='/uploadaudio'; ?>
- <?php $delete_url='/deleteaudio'; ?>
- <?php $chunksize=100000; ?>
Définit les URL de la sauvegarde et de la destruction d'une audio sur le serveur, la taille des blocs de données envoyés au serveur.
- <?php $id=uniqid('id'); ?>
Définit l'identifiant de la <div>
qui encadre le HTML du lecteur audio.
- <div id="<?php echo $id; ?>" class="noprint">
- <audio controls preload="metadata">
- <source src="/files/sounds/thanatos.mp3" type="audio/mpeg" />
- <source src="/files/sounds/thanatos.ogg" type="audio/ogg" />
- </audio>
- </div>
Définit un élément <audio>
dont les paramètres <source>
serviront à configurer le lecteur audio.
Cet élément sera caché par le code en JavaScript.
- <?php head('javascript', '/objectivejs/Objective.js'); ?>
- <?php head('javascript', '/objectivejs/Responder.js'); ?>
- <?php head('javascript', '/objectivejs/View.js'); ?>
- <?php head('javascript', '/objectivejs/AudioPlayer.js'); ?>
Inclut le code de toutes les classes nécessaires.
RAPPEL : La fonction head
de la librairie iZend ajoute une balise telle que <script src="/objectivejs/Objective.js"></script>
à la section <head>
du document HTML. Adaptez le code à votre environnement de développement.
- AudioPlayer.prototype.createWidget = function() {
- const htmlaudiocontrols = [
- '<span class="audiocontrols">',
- '<span class="audioplay"><i class="fas fa-3x fa-play-circle"></i></span>',
- '<span class="audiopause"><i class="fas fa-3x fa-pause-circle"></i></span>',
- '<input class="audiobar" type="range" min="0" max="100" step="1" value="0"/>',
- '<span class="audiotime">00:00:00</span>',
- '<span class="audioloop"><i class="fas fa-sm fa-sync-alt"></i></span>',
- '</span>'
- ].join('\n');
- const htmlmediacontrols = [
- '<span class="mediacontrols">',
- '<span class="recordstart"><i class="fas fa-lg fa-fw fa-microphone"></i></span>',
- '<span class="recordstop"><i class="fas fa-lg fa-fw fa-microphone-alt"></i></span>',
- '<span class="fileload"><i class="fas fa-lg fa-fw fa-file-audio"></i></span>',
- '<span class="audiodelete"><i class="fas fa-lg fa-fw fa-trash"></i></span>',
- '<span class="audioupload"><i class="fas fa-lg fa-fw fa-file-export"></i></span>',
- '<span class="mediastatus"></span>',
- '</span>',
- '<input class="mediafile" type="file" accept="audio/*"/>'
- ].join('\n');
- const html='<div class="ojs_audio">' + '\n' + htmlaudiocontrols + '\n' + htmlmediacontrols + '\n' + '</div>';
- let template = document.createElement('template');
- template.innerHTML = html;
- let widget = template.content.children[0];
- this.setWidget(widget);
- return this;
- }
Définit la méthode createWidget
d'une instance de AudioPlayer.
NOTE : Cet exemple utilise les icônes de Font Awesome.
Adaptez-le à votre environnement de développement.
Le HTML de l'interface peut aussi être créé directement dans le document et passé à setWidget
. Voir AudioPlaylist.
- <?php if (!$debug): ?>
- (function() {
- <?php endif; ?>
Isole tout le code en JavaScript dans une fonction de fermeture si $debug
vaut false
.
- const options = {
- recorder: true,
- load: true,
- draganddrop: true,
- deleteURL: '<?php echo $delete_url; ?>',
- uploadURL: '<?php echo $upload_url; ?>',
- chunksize: <?php echo $chunksize; ?>
- }
- const audioplayer = new AudioPlayer(options);
Configure et crée une instance de AudioPlayer.
NOTE : Jouez avec les options pour voir comment l'interface est configurée. Mettez en commentaire des widgets dans createWidget
pour changer l'apparence du lecteur.
- const container = document.querySelector('#<?php echo $id; ?>');
Récupère la <div>
qui encadre le HTML du programme.
- const audio = container.querySelector('audio');
- audioplayer.setFromAudio(audio);
Configure la source audio du lecteur avec l'élément <audio>
d'origine.
- audioplayer.createManagedWidget(container).resetWidget();
Crée et affiche l'interface du lecteur audio.
- audio.hidden = true;
Cache l'élément <audio>
d'origine.
- <?php if (!$debug): ?>
- })();
- <?php endif; ?>
Ferme la fonction qui isole le code en JavaScript si $debug
vaut false
.
VOIR AUSSI
View, AudioPlaylist, Wall, Écrire des données sur un serveur
Commentaires