UNPKG

flutter_sound_core

Version:
402 lines (329 loc) 17.8 kB
/* * Copyright 2018, 2019, 2020 Dooboolab. * Copyright 2021, 2022, 2023, 2024 Canardoux. * * This file is part of Flutter-Sound. * * Flutter-Sound is free software: you can redistribute it and/or modify * it under the terms of the Mozilla Public License version 2 (MPL-2.0), * as published by the Mozilla organization. * * Flutter-Sound is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MPL General Public License for more details. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ const PLAYER_VERSION = '9.26.0' function newPlayerInstance(aCallback, callbackTable) { return new FlutterSoundPlayer(aCallback, callbackTable); } const IS_PLAYER_STOPPED = 0; const IS_PLAYER_PLAYING = 1; const IS_PLAYER_PAUSED = 2; const CB_updateProgress = 0; const CB_updatePlaybackState = 1; const CB_needSomeFood = 2; const CB_audioPlayerFinished = 3; const CB_startPlayerCompleted = 4; const CB_pausePlayerCompleted = 5; const CB_resumePlayerCompleted = 6; const CB_stopPlayerCompleted = 7; const CB_openPlayerCompleted = 8; const CB_player_log = 9; var instanceNumber = 1; class FlutterSoundPlayer { static newInstance(aCallback, callbackTable) { return new FlutterSoundPlayer(aCallback, callbackTable); } constructor(aCallback, callbackTable) { this.callback = aCallback; this.callbackTable = callbackTable; this.howl = null; this.temporaryBlob = null; this.status = IS_PLAYER_STOPPED; this.subscriptionDuration = 0; this.duration = 0; this.instanceNo = instanceNumber; this.callbackTable[CB_player_log](this.callback, DBG, 'Instance Number : ' + this.instanceNo.toString()) ++instanceNumber; } initializeMediaPlayer(focus, category, mode, audioFlags, device, withUI) { this.status = IS_PLAYER_STOPPED; this.callbackTable[CB_openPlayerCompleted](this.callback, this.getPlayerState(), true); return this.getPlayerState(); } releaseMediaPlayer() { this.status = IS_PLAYER_STOPPED; return this.getPlayerState(); } playAudioFromURL(path, codec) { this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> playAudioFromURL : ' + path); var me = this; var howl = new Howl ({ src: [path], format: tabFormat[codec], onload: function () { me.callbackTable[CB_player_log](me.callback, DBG, 'onload'); me.howl.play(); }, onplay: function () { me.callbackTable[CB_player_log](me.callback, DBG, 'onplay'); me.duration = Math.ceil(howl.duration() * 1000); me.status = IS_PLAYER_PLAYING; if (me.pauseResume != IS_PLAYER_PAUSED) { me.callbackTable[CB_startPlayerCompleted](me.callback, me.getPlayerState(), true, me.duration); // Duration is unknown } else { me.callbackTable[CB_resumePlayerCompleted](me.callback, me.getPlayerState(), true); } me.startTimer(); }, onplayerror: function () { me.callbackTable[CB_player_log](me.callback, ERROR, 'onplayerror'); me.stop(); //me.callbackTable[CB_startPlayerCompleted](me.callback, me.getPlayerState(), false, -1); me.callbackTable[CB_startPlayerCompleted](me.callback, me.getPlayerState(), false, -1); }, onend: function () { me.callbackTable[CB_player_log](me.callback, DBG, 'onend'); me.stop(); me.status = IS_PLAYER_STOPPED; me.callbackTable[CB_audioPlayerFinished](me.callback, me.getPlayerState()); }, onloaderror: function () { me.callbackTable[CB_player_log](me.callback, ERROR, 'onloaderror'); me.stop() me.callbackTable[CB_startPlayerCompleted](me.callback, me.getPlayerState(), false, -1);; }, onpause: function () { me.callbackTable[CB_player_log](me.callback, DBG, 'onpause'); me.status = IS_PLAYER_PAUSED; me.callbackTable[CB_pausePlayerCompleted](me.callback, me.getPlayerState(), true); }, onstop: function () { me.callbackTable[CB_player_log](me.callback, DBG, 'onstop'); me.status = IS_PLAYER_STOPPED; me.howl = null; //me.callbackTable[CB_stopPlayerCompleted](me.callback, me.getPlayerState(), true); }, onseek: function () { //me.callbackTable[CB_player_log](me.callback, DBG, 'onseek'); }, }); this.howl = howl; if (this.latentVolume != null && this.latentVolume >= 0) this.howl.volume(this.latentVolume); if (this.latentSpeed != null && this.latentSpeed >= 0) this.howl.rate(this.latentSpeed); if (this.latentSeek != null && this.latentSeek >= 0) this.seekToPlayer(this.latentSeek); this.pauseResume = IS_PLAYER_PLAYING; // howl.play(); // This now done in 'onload' this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- playAudioFromURL'); return this.getPlayerState(); } setAudioFocus(focus, category, mode, audioFlags, device,) { return this.getPlayerState(); } isDecoderSupported(codec,) { return true; // TODO } setSubscriptionDuration(duration) { this.callbackTable[CB_player_log](this.callback, DBG, 'setSubscriptionDuration'); this.subscriptionDuration = duration; if (duration > 0 && this.howl != null) this.startTimer(); return this.getPlayerState(); } getRecordURL(path,) { var myStorage; if ((path == null) || (path == '')) { return null; } if (path.includes("/")) return path; if (path.substring(0, 1) == '/') { myStorage = window.localStorage; this.callbackTable[CB_player_log](this.callback, DBG, 'localStorage'); } else { myStorage = window.sessionStorage; this.callbackTable[CB_player_log](this.callback, DBG, 'sessionStorage'); } var url = myStorage.getItem(path); return url } startPlayer(codec, fromDataBuffer, fromURI, numChannels, sampleRate, bufferSize) { this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> startPlayer'); this.stop(); if (this.temporaryBlob != null) { URL.revokeObjectURL(this.temporaryBlob); this.temporaryBlob = null; } if (fromDataBuffer != null) { this.callbackTable[CB_player_log](this.callback, DBG, 'startPlayer : ' + fromDataBuffer.constructor.name); var anArray = [fromDataBuffer]; // new Array(fromDataBuffer); // return this.playAudioFromBuffer(fromDataBuffer.buffer); // playAudioFromBuffer() is ctually not used var blob = new Blob(anArray, { 'type': mime_types[codec] }); fromURI = URL.createObjectURL(blob); this.temporaryBlob = fromURI; } if (fromURI == null || fromURI == '') { fromURI = lastUrl; this.callbackTable[CB_player_log](this.callback, DBG, 'Playing lastUrl : ' + lastUrl); } this.callbackTable[CB_player_log](this.callback, DBG, 'startPlayer : ' + fromURI); var url = this.getRecordURL(fromURI); if (url != null) { this.callbackTable[CB_player_log](this.callback, DBG, 'startPlayer : ' + url.constructor.name); fromURI = url; } //this.deltaTime = 0; this.pauseResume = IS_PLAYER_PLAYING; // Maybe too early this.playAudioFromURL(url, codec); this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- startPlayer'); return this.getPlayerState(); } feed(data,) { return this.getPlayerState(); } startPlayerFromTrack(progress, duration, track, canPause, canSkipForward, canSkipBackward, defaultPauseResume, removeUIWhenStopped,) { return 0; // TODO } nowPlaying(progress, duration, track, canPause, canSkipForward, canSkipBackward, defaultPauseResume,) { return this.getPlayerState(); } stop() { this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> stop'); this.stopTimer(); if (this.temporaryBlob != null) URL.revokeObjectURL(this.temporaryBlob); this.temporaryBlob = null; if (this.howl != null) { this.howl.stop(); this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- stop : true'); return true; } else { this.status = IS_PLAYER_STOPPED; // Maybe too early ? //this.callbackTable[CB_stopPlayerCompleted](this.callback, IS_PLAYER_STOPPED, true); this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- stop : false'); return false; } } stopPlayer() { this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> stopPlayer'); //if (this.howl == null) if (!this.stop()) {} //this.callbackTable[CB_stopPlayerCompleted](this.callback, this.getPlayerState(), true); this.status = IS_PLAYER_STOPPED; // Added in 10.2.5 because of [fs #1179] this.callbackTable[CB_stopPlayerCompleted](this.callback, IS_PLAYER_STOPPED, true); this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- stopPlayer'); return this.getPlayerState(); } getPlayerState() { if (this.howl == null) { this.status = IS_PLAYER_STOPPED; } return this.status; } pausePlayer() { this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> pausePlayer'); this.stopTimer(); if (this.getPlayerState() == IS_PLAYER_PLAYING) { //this.status = IS_PLAYER_PAUSED; // Maybe too early this.howl.pause(); } else { this.callbackTable[CB_pausePlayerCompleted](this.callback, this.getPlayerState(), false); } this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- pausePlayer'); return this.getPlayerState(); } resumePlayer() { this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> resumePlayer'); if (this.getPlayerState() == IS_PLAYER_PAUSED) { //this.status = IS_PLAYER_PLAYING; // Maybe too early this.pauseResume = IS_PLAYER_PAUSED; this.howl.play(); } else { this.callbackTable[CB_resumePlayerCompleted](this.callback, this.getPlayerState(), false); } this.startTimer(); this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- resumePlayer'); return this.getPlayerState(); } seekToPlayer(duration) { this.callbackTable[CB_player_log](this.callback, DBG, '---> seekToPlayer()'); if (this.howl != null) { this.latentSeek = 0; this.countDownDate = new Date().getTime() - duration; //this.deltaTime = 0; this.howl.seek(duration / 1000); } else this.latentSeek = duration; this.callbackTable[CB_player_log](this.callback, DBG, '<--- seekToPlayer()'); return this.getPlayerState(); } setVolume(volume) { this.callbackTable[CB_player_log](this.callback, DBG, '---> setVolume()'); this.latentVolume = volume; if (this.howl != null) this.howl.volume(volume); this.callbackTable[CB_player_log](this.callback, DBG, '<--- setVolume()'); return this.getPlayerState(); } setVolumePan(volume, pan) { this.callbackTable[CB_player_log](this.callback, DBG, '---> setVolumePan()'); // Set the latent volume this.latentVolume = volume; this.latentPan = pan; // If howl instance exists, set volume and pan if (this.howl != null) { this.howl.volume(volume); // Set panning; if it's a stereo sound, use stereo, otherwise use pos for 3D if (typeof this.howl.stereo === 'function') { this.howl.stereo(pan); // For stereo sounds } else if (typeof this.howl.pos === 'function') { this.howl.pos(pan, 0, 0); // For 3D spatial sounds, you might want to adjust other coordinates as needed } } this.callbackTable[CB_player_log](this.callback, DBG, '<--- setVolumePan()'); return this.getPlayerState(); } setSpeed(speed) { this.callbackTable[CB_player_log](this.callback, DBG, '---> setSpeed()'); this.latentSpeed = speed; if (this.howl != null) this.howl.rate(speed); this.callbackTable[CB_player_log](this.callback, DBG, '<--- setSpeed()'); return this.getPlayerState(); } setUIProgressBar(duration, progress) { return this.getPlayerState(); } startTimer() { this.callbackTable[CB_player_log](this.callback, DBG, '---> startTimer()'); this.stopTimer(); var me = this; if (this.subscriptionDuration > 0) { this.countDownDate = new Date().getTime(); this.timerId = setInterval ( function () { var pos = Math.floor(me.howl.seek() * 1000); if (pos > me.duration) pos = me.duration; me.callbackTable[CB_updateProgress](me.callback, pos/*me.deltaTime + distance*/, me.duration); }, this.subscriptionDuration ); } this.callbackTable[CB_player_log](this.callback, DBG, '<--- startTimer()'); } stopTimer() { this.callbackTable[CB_player_log](this.callback, DBG, 'stopTimer()'); if (this.timerId != null) { clearInterval(this.timerId); this.timerId = null; } } }