mpvAudioPlayer.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  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._hasConnection = false;
  44. self.play = (options) => {
  45. self._started = false;
  46. self._timeUpdated = false;
  47. self._currentTime = null;
  48. self._duration = undefined;
  49. const player = window.api.player;
  50. if (!self._hasConnection) {
  51. self._hasConnection = true;
  52. player.playing.connect(onPlaying);
  53. player.positionUpdate.connect(onTimeUpdate);
  54. player.finished.connect(onEnded);
  55. player.updateDuration.connect(onDuration);
  56. player.error.connect(onError);
  57. player.paused.connect(onPause);
  58. window.api.taskbar.pauseClicked.connect(onPauseClicked);
  59. }
  60. return setCurrentSrc(options);
  61. };
  62. function setCurrentSrc(options) {
  63. return new Promise((resolve) => {
  64. const val = options.url;
  65. self._currentSrc = val;
  66. console.debug('playing url: ' + val);
  67. // Convert to seconds
  68. const ms = (options.playerStartPositionTicks || 0) / 10000;
  69. self._currentPlayOptions = options;
  70. window.api.player.load(val,
  71. { startMilliseconds: ms, autoplay: true },
  72. {type: 'music', headers: {'User-Agent': 'JellyfinMediaPlayer'}, media: {}},
  73. '#1',
  74. '',
  75. resolve);
  76. });
  77. }
  78. self.onEndedInternal = () => {
  79. const stopInfo = {
  80. src: self._currentSrc
  81. };
  82. self.events.trigger(self, 'stopped', [stopInfo]);
  83. window.api.taskbar.setControlsVisible(false);
  84. self._currentTime = null;
  85. self._currentSrc = null;
  86. self._currentPlayOptions = null;
  87. };
  88. self.stop = (destroyPlayer) => {
  89. cancelFadeTimeout();
  90. const src = self._currentSrc;
  91. if (src) {
  92. const originalVolume = self._volume;
  93. return fade(self, null, self._volume).then(function () {
  94. self.pause();
  95. self.setVolume(originalVolume, false);
  96. self.onEndedInternal();
  97. if (destroyPlayer) {
  98. self.destroy();
  99. }
  100. });
  101. }
  102. return Promise.resolve();
  103. };
  104. self.destroy = () => {
  105. window.api.player.stop();
  106. const player = window.api.player;
  107. self._hasConnection = false;
  108. player.playing.disconnect(onPlaying);
  109. player.positionUpdate.disconnect(onTimeUpdate);
  110. player.finished.disconnect(onEnded);
  111. self._duration = undefined;
  112. player.updateDuration.disconnect(onDuration);
  113. player.error.disconnect(onError);
  114. player.paused.disconnect(onPause);
  115. window.api.taskbar.pauseClicked.disconnect(onPauseClicked);
  116. };
  117. function onDuration(duration) {
  118. self._duration = duration;
  119. }
  120. function onEnded() {
  121. self.onEndedInternal();
  122. }
  123. function onTimeUpdate(time) {
  124. // Don't trigger events after user stop
  125. if (!self._isFadingOut) {
  126. self._currentTime = time;
  127. self.events.trigger(self, 'timeupdate');
  128. window.api.taskbar.setProgress(time * 100 / self._duration);
  129. }
  130. }
  131. function onPlaying() {
  132. if (!self._started) {
  133. self._started = true;
  134. window.api.taskbar.setControlsVisible(true);
  135. }
  136. self.setPlaybackRate(1);
  137. self.setMute(false);
  138. if (self._paused) {
  139. self._paused = false;
  140. self.events.trigger(self, 'unpause');
  141. window.api.taskbar.setPaused(false);
  142. }
  143. self.events.trigger(self, 'playing');
  144. }
  145. function onPause() {
  146. self._paused = true;
  147. self.events.trigger(self, 'pause');
  148. window.api.taskbar.setPaused(true);
  149. }
  150. function onError(error) {
  151. console.error(`media element error: ${error}`);
  152. self.events.trigger(self, 'error', [
  153. {
  154. type: 'mediadecodeerror'
  155. }
  156. ]);
  157. }
  158. function onPauseClicked() {
  159. self.paused() ? self.unpause() : self.pause();
  160. }
  161. }
  162. getSavedVolume() {
  163. return this.appSettings.get('volume') || 1;
  164. }
  165. currentSrc() {
  166. return this._currentSrc;
  167. }
  168. canPlayMediaType(mediaType) {
  169. return (mediaType || '').toLowerCase() === 'audio';
  170. }
  171. getDeviceProfile(item, options) {
  172. if (this.appHost.getDeviceProfile) {
  173. return this.appHost.getDeviceProfile(item, options);
  174. }
  175. return {};
  176. }
  177. currentTime(val) {
  178. if (val != null) {
  179. window.api.player.seekTo(val);
  180. return;
  181. }
  182. return this._currentTime;
  183. }
  184. currentTimeAsync() {
  185. return new Promise((resolve) => {
  186. window.api.player.getPosition(resolve);
  187. });
  188. }
  189. duration() {
  190. if (this._duration) {
  191. return this._duration;
  192. }
  193. return null;
  194. }
  195. seekable() {
  196. return Boolean(this._duration);
  197. }
  198. getBufferedRanges() {
  199. return [];
  200. }
  201. pause() {
  202. window.api.player.pause();
  203. }
  204. // This is a retry after error
  205. resume() {
  206. this._paused = false;
  207. window.api.player.play();
  208. }
  209. unpause() {
  210. window.api.player.play();
  211. }
  212. paused() {
  213. return this._paused;
  214. }
  215. setPlaybackRate(value) {
  216. this._playRate = value;
  217. window.api.player.setPlaybackRate(value * 1000);
  218. }
  219. getPlaybackRate() {
  220. return this._playRate;
  221. }
  222. getSupportedPlaybackRates() {
  223. return [{
  224. name: '0.5x',
  225. id: 0.5
  226. }, {
  227. name: '0.75x',
  228. id: 0.75
  229. }, {
  230. name: '1x',
  231. id: 1.0
  232. }, {
  233. name: '1.25x',
  234. id: 1.25
  235. }, {
  236. name: '1.5x',
  237. id: 1.5
  238. }, {
  239. name: '1.75x',
  240. id: 1.75
  241. }, {
  242. name: '2x',
  243. id: 2.0
  244. }];
  245. }
  246. saveVolume(value) {
  247. if (value) {
  248. this.appSettings.set('volume', value);
  249. }
  250. }
  251. setVolume(val, save = true) {
  252. this._volume = val;
  253. if (save) {
  254. this.saveVolume((val || 100) / 100);
  255. this.events.trigger(this, 'volumechange');
  256. }
  257. window.api.player.setVolume(val);
  258. }
  259. getVolume() {
  260. return this._volume;
  261. }
  262. volumeUp() {
  263. this.setVolume(Math.min(this.getVolume() + 2, 100));
  264. }
  265. volumeDown() {
  266. this.setVolume(Math.max(this.getVolume() - 2, 0));
  267. }
  268. setMute(mute) {
  269. this._muted = mute;
  270. window.api.player.setMuted(mute);
  271. }
  272. isMuted() {
  273. return this._muted;
  274. }
  275. supports(feature) {
  276. if (!supportedFeatures) {
  277. supportedFeatures = getSupportedFeatures();
  278. }
  279. return supportedFeatures.indexOf(feature) !== -1;
  280. }
  281. }
  282. let supportedFeatures;
  283. function getSupportedFeatures() {
  284. return ['PlaybackRate'];
  285. }
  286. window._mpvAudioPlayer = mpvAudioPlayer;