flutter_sound_player.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /*
  2. * Copyright 2018, 2019, 2020 Dooboolab.
  3. *
  4. * This file is part of Flutter-Sound.
  5. *
  6. * Flutter-Sound is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License version 3 (LGPL-V3), as published by
  8. * the Free Software Foundation.
  9. *
  10. * Flutter-Sound is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License
  16. * along with Flutter-Sound. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. const PLAYER_VERSION = '8.2.0'
  19. function newPlayerInstance(aCallback, callbackTable) { return new FlutterSoundPlayer(aCallback, callbackTable); }
  20. const IS_PLAYER_STOPPED = 0;
  21. const IS_PLAYER_PLAYING = 1;
  22. const IS_PLAYER_PAUSED = 2;
  23. const CB_updateProgress = 0;
  24. const CB_updatePlaybackState = 1;
  25. const CB_needSomeFood = 2;
  26. const CB_audioPlayerFinished = 3;
  27. const CB_startPlayerCompleted = 4;
  28. const CB_pausePlayerCompleted = 5;
  29. const CB_resumePlayerCompleted = 6;
  30. const CB_stopPlayerCompleted = 7;
  31. const CB_openPlayerCompleted = 8;
  32. const CB_closePlayerCompleted = 9;
  33. const CB_player_log = 10;
  34. var instanceNumber = 1;
  35. class FlutterSoundPlayer {
  36. static newInstance(aCallback, callbackTable) { return new FlutterSoundPlayer(aCallback, callbackTable); }
  37. constructor(aCallback, callbackTable) {
  38. this.callback = aCallback;
  39. this.callbackTable = callbackTable;
  40. this.howl = null;
  41. this.temporaryBlob = null;
  42. this.status = IS_PLAYER_STOPPED;
  43. //this.deltaTime = 0;
  44. this.subscriptionDuration = 0;
  45. this.duration = 0;
  46. this.instanceNo = instanceNumber;
  47. this.callbackTable[CB_player_log](this.callback, DBG, 'Instance Number : ' + this.instanceNo.toString())
  48. ++instanceNumber;
  49. }
  50. initializeMediaPlayer(focus, category, mode, audioFlags, device, withUI) {
  51. //this.callback.openAudioSessionCompleted(true);
  52. this.status = IS_PLAYER_STOPPED;
  53. this.callbackTable[CB_openPlayerCompleted](this.callback, this.getPlayerState(), true);
  54. return this.getPlayerState();
  55. }
  56. releaseMediaPlayer() {
  57. this.status = IS_PLAYER_STOPPED;
  58. this.callbackTable[CB_closePlayerCompleted](this.callback, this.getPlayerState(), true);
  59. return this.getPlayerState();
  60. }
  61. playAudioFromURL(path, codec) {
  62. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> playAudioFromURL : ' + path);
  63. var me = this;
  64. var howl = new Howl
  65. ({
  66. src: [path],
  67. format: tabFormat[codec],
  68. onload: function () {
  69. me.callbackTable[CB_player_log](me.callback, DBG, 'onload');
  70. me.howl.play();
  71. },
  72. onplay: function () {
  73. me.callbackTable[CB_player_log](me.callback, DBG, 'onplay');
  74. me.duration = Math.ceil(howl.duration() * 1000);
  75. me.status = IS_PLAYER_PLAYING;
  76. if (me.pauseResume != IS_PLAYER_PAUSED) {
  77. me.callbackTable[CB_startPlayerCompleted](me.callback, me.getPlayerState(), true, me.duration); // Duration is unknown
  78. } else {
  79. me.callbackTable[CB_resumePlayerCompleted](me.callback, me.getPlayerState(), true);
  80. }
  81. me.startTimer();
  82. },
  83. onplayerror: function () {
  84. me.callbackTable[CB_player_log](me.callback, ERROR, 'onplayerror');
  85. me.stop();
  86. },
  87. onend: function () {
  88. me.callbackTable[CB_player_log](me.callback, DBG, 'onend');
  89. me.stop();
  90. me.status = IS_PLAYER_STOPPED;
  91. me.callbackTable[CB_audioPlayerFinished](me.callback, me.getPlayerState());
  92. },
  93. onloaderror: function () {
  94. me.callbackTable[CB_player_log](me.callback, ERROR, 'onloaderror');
  95. me.stop()
  96. },
  97. onpause: function () {
  98. me.callbackTable[CB_player_log](me.callback, DBG, 'onpause');
  99. me.status = IS_PLAYER_PAUSED;
  100. me.callbackTable[CB_pausePlayerCompleted](me.callback, me.getPlayerState(), true);
  101. },
  102. onstop: function () {
  103. me.callbackTable[CB_player_log](me.callback, DBG, 'onstop');
  104. me.status = IS_PLAYER_STOPPED;
  105. me.howl = null;
  106. me.callbackTable[CB_stopPlayerCompleted](me.callback, me.getPlayerState(), true);
  107. },
  108. onseek: function () {
  109. //me.callbackTable[CB_player_log](me.callback, DBG, 'onseek');
  110. },
  111. });
  112. this.howl = howl;
  113. if (this.latentVolume != null && this.latentVolume >= 0)
  114. this.howl.volume(this.latentVolume);
  115. if (this.latentSpeed != null && this.latentSpeed >= 0)
  116. this.howl.rate(this.latentSpeed);
  117. if (this.latentSeek != null && this.latentSeek >= 0)
  118. this.seekToPlayer(this.latentSeek);
  119. this.pauseResume = IS_PLAYER_PLAYING;
  120. // howl.play(); // This now done in 'onload'
  121. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- playAudioFromURL');
  122. return this.getPlayerState();
  123. }
  124. /* ACTUALLY NOT USED
  125. playAudioFromBuffer(dataBuffer) // Actually not used
  126. {
  127. var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  128. var source = audioCtx.createBufferSource();
  129. me.callbackTable[CB_player_log](me.callback, DBG, dataBuffer.constructor.name)
  130. audioCtx.decodeAudioData
  131. (
  132. dataBuffer, //dataBuffer.buffer,
  133. function(buffer)
  134. {
  135. source.buffer = buffer;
  136. source.connect(audioCtx.destination);
  137. source.loop = false;
  138. // start the source playing
  139. source.start();
  140. },
  141. function(e){ me.callbackTable[CB_player_log](me.callback, DBG, "Error with decoding audio data" + e.err); }
  142. );
  143. this.callback.startPlayerCompleted(777);
  144. return 0; // playAudioFromBuffer() does not support sound Duration
  145. }
  146. */
  147. setAudioFocus(focus, category, mode, audioFlags, device,) {
  148. return this.getPlayerState();
  149. }
  150. isDecoderSupported(codec,) {
  151. return true; // TODO
  152. }
  153. setSubscriptionDuration(duration) {
  154. this.callbackTable[CB_player_log](this.callback, DBG, 'setSubscriptionDuration');
  155. this.subscriptionDuration = duration;
  156. if (duration > 0 && this.howl != null)
  157. this.startTimer();
  158. return this.getPlayerState();
  159. }
  160. getRecordURL(path,) {
  161. var myStorage;
  162. if ((path == null) || (path == '')) {
  163. return null;
  164. }
  165. if (path.includes("/"))
  166. return path;
  167. if (path.substring(0, 1) == '/') {
  168. myStorage = window.localStorage;
  169. this.callbackTable[CB_player_log](this.callback, DBG, 'localStorage');
  170. } else {
  171. myStorage = window.sessionStorage;
  172. this.callbackTable[CB_player_log](this.callback, DBG, 'sessionStorage');
  173. }
  174. var url = myStorage.getItem(path);
  175. return url
  176. }
  177. startPlayer(codec, fromDataBuffer, fromURI, numChannels, sampleRate) {
  178. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> startPlayer');
  179. this.stop();
  180. if (this.temporaryBlob != null) {
  181. URL.revokeObjectURL(this.temporaryBlob);
  182. this.temporaryBlob = null;
  183. }
  184. if (fromDataBuffer != null) {
  185. this.callbackTable[CB_player_log](this.callback, DBG, 'startPlayer : ' + fromDataBuffer.constructor.name);
  186. var anArray = [fromDataBuffer]; // new Array(fromDataBuffer);
  187. // return this.playAudioFromBuffer(fromDataBuffer.buffer); // playAudioFromBuffer() is ctually not used
  188. var blob = new Blob(anArray, { 'type': mime_types[codec] });
  189. fromURI = URL.createObjectURL(blob);
  190. this.temporaryBlob = fromURI;
  191. }
  192. if (fromURI == null || fromURI == '') {
  193. fromURI = lastUrl;
  194. this.callbackTable[CB_player_log](this.callback, DBG, 'Playing lastUrl : ' + lastUrl);
  195. }
  196. this.callbackTable[CB_player_log](this.callback, DBG, 'startPlayer : ' + fromURI);
  197. var url = this.getRecordURL(fromURI);
  198. if (url != null) {
  199. this.callbackTable[CB_player_log](this.callback, DBG, 'startPlayer : ' + url.constructor.name);
  200. fromURI = url;
  201. }
  202. //this.deltaTime = 0;
  203. this.pauseResume = IS_PLAYER_PLAYING; // Maybe too early
  204. this.playAudioFromURL(url, codec);
  205. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- startPlayer');
  206. return this.getPlayerState();
  207. }
  208. feed(data,) {
  209. return this.getPlayerState();
  210. }
  211. startPlayerFromTrack(progress, duration, track, canPause, canSkipForward, canSkipBackward, defaultPauseResume, removeUIWhenStopped,) {
  212. return 0; // TODO
  213. }
  214. nowPlaying(progress, duration, track, canPause, canSkipForward, canSkipBackward, defaultPauseResume,) {
  215. return this.getPlayerState();
  216. }
  217. stop() {
  218. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> stop');
  219. this.stopTimer();
  220. if (this.temporaryBlob != null)
  221. URL.revokeObjectURL(this.temporaryBlob);
  222. this.temporaryBlob = null;
  223. if (this.howl != null) {
  224. this.howl.stop();
  225. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- stop');
  226. return true;
  227. }
  228. else {
  229. this.status = IS_PLAYER_STOPPED; // Maybe too early ?
  230. //this.callbackTable[CB_stopPlayerCompleted](this.callback, IS_PLAYER_STOPPED, true);
  231. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- stop');
  232. return false;
  233. }
  234. }
  235. stopPlayer() {
  236. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> stopPlayer');
  237. //if (this.howl == null)
  238. //this.callbackTable[CB_stopPlayerCompleted](this.callback, IS_PLAYER_STOPPED, true);
  239. if (!this.stop())
  240. this.callbackTable[CB_stopPlayerCompleted](this.callback, this.getPlayerState(), true);
  241. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- stopPlayer');
  242. return this.getPlayerState();
  243. }
  244. getPlayerState() {
  245. if (this.howl == null) {
  246. this.status = IS_PLAYER_STOPPED;
  247. }
  248. return this.status;
  249. }
  250. pausePlayer() {
  251. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> pausePlayer');
  252. this.stopTimer();
  253. if (this.getPlayerState() == IS_PLAYER_PLAYING) {
  254. //this.status = IS_PLAYER_PAUSED; // Maybe too early
  255. this.howl.pause();
  256. } else {
  257. this.callbackTable[CB_pausePlayerCompleted](this.callback, this.getPlayerState(), false);
  258. }
  259. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- pausePlayer');
  260. return this.getPlayerState();
  261. }
  262. resumePlayer() {
  263. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: ---> resumePlayer');
  264. if (this.getPlayerState() == IS_PLAYER_PAUSED) {
  265. //this.status = IS_PLAYER_PLAYING; // Maybe too early
  266. this.pauseResume = IS_PLAYER_PAUSED;
  267. this.howl.play();
  268. } else {
  269. this.callbackTable[CB_resumePlayerCompleted](this.callback, this.getPlayerState(), false);
  270. }
  271. this.startTimer();
  272. this.callbackTable[CB_player_log](this.callback, DBG, 'JS: <--- resumePlayer');
  273. return this.getPlayerState();
  274. }
  275. seekToPlayer(duration) {
  276. this.callbackTable[CB_player_log](this.callback, DBG, '---> seekToPlayer()');
  277. if (this.howl != null) {
  278. this.latentSeek = 0;
  279. this.countDownDate = new Date().getTime() - duration;
  280. //this.deltaTime = 0;
  281. this.howl.seek(duration / 1000);
  282. } else
  283. this.latentSeek = duration;
  284. this.callbackTable[CB_player_log](this.callback, DBG, '<--- seekToPlayer()');
  285. return this.getPlayerState();
  286. }
  287. setVolume(volume) {
  288. this.callbackTable[CB_player_log](this.callback, DBG, '---> setVolume()');
  289. this.latentVolume = volume;
  290. if (this.howl != null)
  291. this.howl.volume(volume);
  292. this.callbackTable[CB_player_log](this.callback, DBG, '<--- setVolume()');
  293. return this.getPlayerState();
  294. }
  295. setSpeed(speed) {
  296. this.callbackTable[CB_player_log](this.callback, DBG, '---> setSpeed()');
  297. this.latentSpeed = speed;
  298. if (this.howl != null)
  299. this.howl.rate(speed);
  300. this.callbackTable[CB_player_log](this.callback, DBG, '<--- setSpeed()');
  301. return this.getPlayerState();
  302. }
  303. setUIProgressBar(duration, progress) {
  304. return this.getPlayerState();
  305. }
  306. startTimer() {
  307. this.callbackTable[CB_player_log](this.callback, DBG, '---> startTimer()');
  308. this.stopTimer();
  309. var me = this;
  310. if (this.subscriptionDuration > 0) {
  311. this.countDownDate = new Date().getTime();
  312. this.timerId = setInterval
  313. (
  314. function () {
  315. //var now = new Date().getTime();
  316. //var distance = now - me.countDownDate;
  317. //distance += me.deltaTime;
  318. var pos = Math.floor(me.howl.seek() * 1000);
  319. if (pos > me.duration)
  320. pos = me.duration;
  321. me.callbackTable[CB_updateProgress](me.callback, pos/*me.deltaTime + distance*/, me.duration);
  322. },
  323. this.subscriptionDuration
  324. );
  325. }
  326. this.callbackTable[CB_player_log](this.callback, DBG, '<--- startTimer()');
  327. }
  328. stopTimer() {
  329. this.callbackTable[CB_player_log](this.callback, DBG, 'stopTimer()');
  330. if (this.timerId != null) {
  331. clearInterval(this.timerId);
  332. //var now = new Date().getTime();
  333. //var distance = now - this.countDownDate;
  334. //this.deltaTime += distance;
  335. this.timerId = null;
  336. }
  337. }
  338. }