mpvAudioPlayer.js 8.6 KB

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