mpvAudioPlayer.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. let fadeTimeout;
  2. function fade(instance, elem, startingVolume) {
  3. instance._isFadingOut = true;
  4. // Need to record the starting volume on each pass rather than querying elem.volume
  5. // This is due to iOS safari not allowing volume changes and always returning the system volume value
  6. const newVolume = Math.max(0, startingVolume - 15);
  7. console.debug('fading volume to ' + newVolume);
  8. api.player.setVolume(newVolume);
  9. if (newVolume <= 0) {
  10. instance._isFadingOut = false;
  11. return Promise.resolve();
  12. }
  13. return new Promise(function (resolve, reject) {
  14. cancelFadeTimeout();
  15. fadeTimeout = setTimeout(function () {
  16. fade(instance, null, newVolume).then(resolve, reject);
  17. }, 100);
  18. });
  19. }
  20. function cancelFadeTimeout() {
  21. const timeout = fadeTimeout;
  22. if (timeout) {
  23. clearTimeout(timeout);
  24. fadeTimeout = null;
  25. }
  26. }
  27. class mpvAudioPlayer {
  28. constructor({ events, appHost, appSettings }) {
  29. const self = this;
  30. self.events = events;
  31. self.appHost = appHost;
  32. self.appSettings = appSettings;
  33. self.name = 'MPV Audio Player';
  34. self.type = 'mediaplayer';
  35. self.id = 'mpvaudioplayer';
  36. self.syncPlayWrapAs = 'htmlaudioplayer';
  37. self.useServerPlaybackInfoForAudio = true;
  38. self._duration = undefined;
  39. self._currentTime = undefined;
  40. self._paused = false;
  41. self._volume = this.getSavedVolume() * 100;
  42. self._playRate = 1;
  43. self.play = (options) => {
  44. self._started = false;
  45. self._timeUpdated = false;
  46. self._currentTime = null;
  47. self._duration = undefined;
  48. const player = window.api.player;
  49. player.playing.connect(onPlaying);
  50. player.positionUpdate.connect(onTimeUpdate);
  51. player.finished.connect(onEnded);
  52. player.updateDuration.connect(onDuration);
  53. player.error.connect(onError);
  54. player.paused.connect(onPause);
  55. return setCurrentSrc(options);
  56. };
  57. function setCurrentSrc(options) {
  58. return new Promise((resolve) => {
  59. const val = options.url;
  60. self._currentSrc = val;
  61. console.debug('playing url: ' + val);
  62. // Convert to seconds
  63. const ms = (options.playerStartPositionTicks || 0) / 10000;
  64. self._currentPlayOptions = options;
  65. window.api.player.load(val,
  66. { startMilliseconds: ms, autoplay: true },
  67. {type: 'music', headers: {'User-Agent': 'JellyfinMediaPlayer'}, media: {}},
  68. '#1',
  69. '',
  70. resolve);
  71. });
  72. }
  73. self.onEndedInternal = () => {
  74. const stopInfo = {
  75. src: self._currentSrc
  76. };
  77. self.events.trigger(self, 'stopped', [stopInfo]);
  78. self._currentTime = null;
  79. self._currentSrc = null;
  80. self._currentPlayOptions = null;
  81. };
  82. self.stop = (destroyPlayer) => {
  83. cancelFadeTimeout();
  84. const src = self._currentSrc;
  85. if (src) {
  86. const originalVolume = self._volume;
  87. return fade(self, null, self._volume).then(function () {
  88. self.pause();
  89. self.setVolume(originalVolume, false);
  90. self.onEndedInternal();
  91. if (destroyPlayer) {
  92. self.destroy();
  93. }
  94. });
  95. }
  96. return Promise.resolve();
  97. };
  98. self.destroy = () => {
  99. window.api.player.stop();
  100. const player = window.api.player;
  101. player.playing.disconnect(onPlaying);
  102. player.positionUpdate.disconnect(onTimeUpdate);
  103. player.finished.disconnect(onEnded);
  104. self._duration = undefined;
  105. player.updateDuration.disconnect(onDuration);
  106. player.error.disconnect(onError);
  107. player.paused.disconnect(onPause);
  108. };
  109. function onDuration(duration) {
  110. self._duration = duration;
  111. }
  112. function onEnded() {
  113. self.onEndedInternal();
  114. }
  115. function onTimeUpdate(time) {
  116. // Don't trigger events after user stop
  117. if (!self._isFadingOut) {
  118. self._currentTime = time;
  119. self.events.trigger(self, 'timeupdate');
  120. }
  121. }
  122. function onPlaying() {
  123. if (!self._started) {
  124. self._started = true;
  125. }
  126. self.setPlaybackRate(1);
  127. self.setMute(false);
  128. if (self._paused) {
  129. self._paused = false;
  130. self.events.trigger(self, 'unpause');
  131. }
  132. self.events.trigger(self, 'playing');
  133. }
  134. function onPause() {
  135. self._paused = true;
  136. self.events.trigger(self, 'pause');
  137. }
  138. function onError(error) {
  139. console.error(`media element error: ${error}`);
  140. self.events.trigger(self, 'error', [
  141. {
  142. type: 'mediadecodeerror'
  143. }
  144. ]);
  145. }
  146. }
  147. getSavedVolume() {
  148. return this.appSettings.get('volume') || 1;
  149. }
  150. currentSrc() {
  151. return this._currentSrc;
  152. }
  153. canPlayMediaType(mediaType) {
  154. return (mediaType || '').toLowerCase() === 'audio';
  155. }
  156. getDeviceProfile(item, options) {
  157. if (this.appHost.getDeviceProfile) {
  158. return this.appHost.getDeviceProfile(item, options);
  159. }
  160. return {};
  161. }
  162. currentTime(val) {
  163. if (val != null) {
  164. window.api.player.seekTo(val);
  165. return;
  166. }
  167. return this._currentTime;
  168. }
  169. currentTimeAsync() {
  170. return new Promise((resolve) => {
  171. window.api.player.getPosition(resolve);
  172. });
  173. }
  174. duration() {
  175. if (this._duration) {
  176. return this._duration;
  177. }
  178. return null;
  179. }
  180. seekable() {
  181. return Boolean(this._duration);
  182. }
  183. getBufferedRanges() {
  184. return [];
  185. }
  186. pause() {
  187. window.api.player.pause();
  188. }
  189. // This is a retry after error
  190. resume() {
  191. this._paused = false;
  192. window.api.player.play();
  193. }
  194. unpause() {
  195. window.api.player.play();
  196. }
  197. paused() {
  198. return this._paused;
  199. }
  200. setPlaybackRate(value) {
  201. this._playRate = value;
  202. window.api.player.setPlaybackRate(value * 1000);
  203. }
  204. getPlaybackRate() {
  205. return this._playRate;
  206. }
  207. getSupportedPlaybackRates() {
  208. return [{
  209. name: '0.5x',
  210. id: 0.5
  211. }, {
  212. name: '0.75x',
  213. id: 0.75
  214. }, {
  215. name: '1x',
  216. id: 1.0
  217. }, {
  218. name: '1.25x',
  219. id: 1.25
  220. }, {
  221. name: '1.5x',
  222. id: 1.5
  223. }, {
  224. name: '1.75x',
  225. id: 1.75
  226. }, {
  227. name: '2x',
  228. id: 2.0
  229. }];
  230. }
  231. saveVolume(value) {
  232. if (value) {
  233. this.appSettings.set('volume', value);
  234. }
  235. }
  236. setVolume(val, save = true) {
  237. this._volume = val;
  238. if (save) {
  239. this.saveVolume((val || 100) / 100);
  240. this.events.trigger(this, 'volumechange');
  241. }
  242. window.api.player.setVolume(val);
  243. }
  244. getVolume() {
  245. return this._volume;
  246. }
  247. volumeUp() {
  248. this.setVolume(Math.min(this.getVolume() + 2, 100));
  249. }
  250. volumeDown() {
  251. this.setVolume(Math.max(this.getVolume() - 2, 0));
  252. }
  253. setMute(mute) {
  254. this._muted = mute;
  255. window.api.player.setMuted(mute);
  256. }
  257. isMuted() {
  258. return this._muted;
  259. }
  260. supports(feature) {
  261. if (!supportedFeatures) {
  262. supportedFeatures = getSupportedFeatures();
  263. }
  264. return supportedFeatures.indexOf(feature) !== -1;
  265. }
  266. }
  267. let supportedFeatures;
  268. function getSupportedFeatures() {
  269. return ['PlaybackRate'];
  270. }
  271. window._mpvAudioPlayer = mpvAudioPlayer;