diff --git a/src/plugins/mpvAudioPlayer/plugin.js b/src/plugins/mpvAudioPlayer/plugin.js deleted file mode 100644 index 27da7313a3..0000000000 --- a/src/plugins/mpvAudioPlayer/plugin.js +++ /dev/null @@ -1,377 +0,0 @@ -import { Events } from 'jellyfin-apiclient'; -import * as htmlMediaHelper from '../../components/htmlMediaHelper'; - -function loadScript(src) { - return new Promise((resolve, reject) => { - const s = document.createElement('script'); - s.src = src; - s.onload = resolve; - s.onerror = reject; - document.head.appendChild(s); - }); -} - -async function createApi() { - await loadScript('qrc:///qtwebchannel/qwebchannel.js'); - const channel = await new Promise((resolve) => { - /*global QWebChannel */ - new QWebChannel(window.qt.webChannelTransport, resolve); - }); - return channel.objects; -} - -async function getApi() { - if (window.apiPromise) { - return await window.apiPromise; - } - - window.apiPromise = createApi(); - return await window.apiPromise; -} - -let fadeTimeout; -function fade(instance, elem, startingVolume) { - instance._isFadingOut = true; - - // Need to record the starting volume on each pass rather than querying elem.volume - // This is due to iOS safari not allowing volume changes and always returning the system volume value - const newVolume = Math.max(0, startingVolume - 15); - console.debug('fading volume to ' + newVolume); - instance.api.player.setVolume(newVolume); - - if (newVolume <= 0) { - instance._isFadingOut = false; - return Promise.resolve(); - } - - return new Promise(function (resolve, reject) { - cancelFadeTimeout(); - fadeTimeout = setTimeout(function () { - fade(instance, null, newVolume).then(resolve, reject); - }, 100); - }); -} - -function cancelFadeTimeout() { - const timeout = fadeTimeout; - if (timeout) { - clearTimeout(timeout); - fadeTimeout = null; - } -} - -class HtmlAudioPlayer { - constructor() { - const self = this; - - self.name = 'Html Audio Player'; - self.type = 'mediaplayer'; - self.id = 'htmlaudioplayer'; - self.useServerPlaybackInfoForAudio = true; - - self._duration = undefined; - self._currentTime = undefined; - self._paused = false; - self._volume = htmlMediaHelper.getSavedVolume() * 100; - self._playRate = 1; - - self.api = undefined; - - self.ensureApi = async () => { - if (!self.api) { - self.api = await getApi(); - } - }; - - self.play = async (options) => { - self._started = false; - self._timeUpdated = false; - self._currentTime = null; - self._duration = undefined; - - await self.ensureApi(); - const player = self.api.player; - player.playing.connect(onPlaying); - player.positionUpdate.connect(onTimeUpdate); - player.finished.connect(onEnded); - player.updateDuration.connect(onDuration); - player.error.connect(onError); - player.paused.connect(onPause); - - return await setCurrentSrc(options); - }; - - function setCurrentSrc(options) { - return new Promise((resolve) => { - const val = options.url; - self._currentSrc = val; - console.debug('playing url: ' + val); - - // Convert to seconds - const ms = (options.playerStartPositionTicks || 0) / 10000; - self._currentPlayOptions = options; - - self.api.player.load(val, - { startMilliseconds: ms, autoplay: true }, - {type: 'music', headers: {'User-Agent': 'JellyfinMediaPlayer'}, media: {}}, - '#1', - '', - resolve); - }); - } - - self.onEndedInternal = () => { - const stopInfo = { - src: self._currentSrc - }; - - Events.trigger(self, 'stopped', [stopInfo]); - - self._currentTime = null; - self._currentSrc = null; - self._currentPlayOptions = null; - }; - - self.stop = async (destroyPlayer) => { - cancelFadeTimeout(); - - const src = self._currentSrc; - - if (src) { - const originalVolume = self._volume; - - await self.ensureApi(); - return await fade(self, null, self._volume).then(function () { - self.pause(); - self.setVolume(originalVolume, false); - - self.onEndedInternal(); - - if (destroyPlayer) { - self.destroy(); - } - }); - } - return; - }; - - self.destroy = async () => { - await self.ensureApi(); - self.api.player.stop(); - - const player = self.api.player; - player.playing.disconnect(onPlaying); - player.positionUpdate.disconnect(onTimeUpdate); - player.finished.disconnect(onEnded); - self._duration = undefined; - player.updateDuration.disconnect(onDuration); - player.error.disconnect(onError); - player.paused.disconnect(onPause); - }; - - function onDuration(duration) { - self._duration = duration; - } - - function onEnded() { - self.onEndedInternal(); - } - - function onTimeUpdate(time) { - // Don't trigger events after user stop - if (!self._isFadingOut) { - self._currentTime = time; - Events.trigger(self, 'timeupdate'); - } - } - - function onPlaying() { - if (!self._started) { - self._started = true; - } - - self.setPlaybackRate(1); - self.setMute(false); - - if (self._paused) { - self._paused = false; - Events.trigger(self, 'unpause'); - } - - Events.trigger(self, 'playing'); - } - - function onPause() { - self._paused = true; - Events.trigger(self, 'pause'); - } - - function onError(error) { - console.error(`media element error: ${error}`); - - htmlMediaHelper.onErrorInternal(self, 'mediadecodeerror'); - } - } - - currentSrc() { - return this._currentSrc; - } - - canPlayMediaType(mediaType) { - return (mediaType || '').toLowerCase() === 'audio'; - } - - getDeviceProfile() { - return Promise.resolve({ - 'Name': 'Jellyfin Media Player', - 'MusicStreamingTranscodingBitrate': 1280000, - 'TimelineOffsetSeconds': 5, - 'TranscodingProfiles': [ - {'Type': 'Audio'} - ], - 'DirectPlayProfiles': [{'Type': 'Audio'}], - 'ResponseProfiles': [], - 'ContainerProfiles': [], - 'CodecProfiles': [], - 'SubtitleProfiles': [] - }); - } - - currentTime(val) { - if (val != null) { - this.ensureApi().then(() => { - this.api.player.seekTo(val); - }); - return; - } - - return this._currentTime; - } - - async currentTimeAsync() { - await this.ensureApi(); - return await new Promise((resolve) => { - this.api.player.getPosition(resolve); - }); - } - - duration() { - if (this._duration) { - return this._duration; - } - - return null; - } - - seekable() { - return Boolean(this._duration); - } - - getBufferedRanges() { - return []; - } - - async pause() { - await this.ensureApi(); - this.api.player.pause(); - } - - // This is a retry after error - async resume() { - await this.ensureApi(); - this._paused = false; - this.api.player.play(); - } - - async unpause() { - await this.ensureApi(); - this.api.player.play(); - } - - paused() { - return this._paused; - } - - async setPlaybackRate(value) { - this._playRate = value; - await this.ensureApi(); - this.api.player.setPlaybackRate(value * 1000); - } - - getPlaybackRate() { - return this._playRate; - } - - getSupportedPlaybackRates() { - return [{ - name: '0.5x', - id: 0.5 - }, { - name: '0.75x', - id: 0.75 - }, { - name: '1x', - id: 1.0 - }, { - name: '1.25x', - id: 1.25 - }, { - name: '1.5x', - id: 1.5 - }, { - name: '1.75x', - id: 1.75 - }, { - name: '2x', - id: 2.0 - }]; - } - - async setVolume(val, save = true) { - this._volume = val; - if (save) { - htmlMediaHelper.saveVolume((val || 100) / 100); - Events.trigger(this, 'volumechange'); - } - await this.ensureApi(); - this.api.player.setVolume(val); - } - - getVolume() { - return this._volume; - } - - volumeUp() { - this.setVolume(Math.min(this.getVolume() + 2, 100)); - } - - volumeDown() { - this.setVolume(Math.max(this.getVolume() - 2, 0)); - } - - async setMute(mute) { - this._muted = mute; - await this.ensureApi(); - this.api.player.setMuted(mute); - } - - isMuted() { - return this._muted; - } - - supports(feature) { - if (!supportedFeatures) { - supportedFeatures = getSupportedFeatures(); - } - - return supportedFeatures.indexOf(feature) !== -1; - } -} - -let supportedFeatures; - -function getSupportedFeatures() { - return ['PlaybackRate']; -} - -export default HtmlAudioPlayer; diff --git a/src/plugins/mpvVideoPlayer/plugin.js b/src/plugins/mpvVideoPlayer/plugin.js deleted file mode 100644 index 5153ac99d5..0000000000 --- a/src/plugins/mpvVideoPlayer/plugin.js +++ /dev/null @@ -1,792 +0,0 @@ -import browser from '../../scripts/browser'; -import { Events } from 'jellyfin-apiclient'; -import loading from '../../components/loading/loading'; -import { appRouter } from '../../components/appRouter'; -import { - saveVolume, - getSavedVolume, - onErrorInternal -} from '../../components/htmlMediaHelper'; -import Screenfull from 'screenfull'; -import globalize from '../../scripts/globalize'; - -function loadScript(src) { - return new Promise((resolve, reject) => { - const s = document.createElement('script'); - s.src = src; - s.onload = resolve; - s.onerror = reject; - document.head.appendChild(s); - }); -} - -async function createApi() { - await loadScript('qrc:///qtwebchannel/qwebchannel.js'); - const channel = await new Promise((resolve) => { - /*global QWebChannel */ - new QWebChannel(window.qt.webChannelTransport, resolve); - }); - return channel.objects; -} - -async function getApi() { - if (window.apiPromise) { - return await window.apiPromise; - } - - window.apiPromise = createApi(); - return await window.apiPromise; -} - -/* eslint-disable indent */ - - function getMediaStreamAudioTracks(mediaSource) { - return mediaSource.MediaStreams.filter(function (s) { - return s.Type === 'Audio'; - }); - } - - export class HtmlVideoPlayer { - /** - * @type {string} - */ - name; - /** - * @type {string} - */ - type = 'mediaplayer'; - /** - * @type {string} - */ - id = 'htmlvideoplayer'; - useFullSubtitleUrls = true; - /** - * @type {boolean} - */ - isFetching = false; - - /** - * @type {HTMLDivElement | null | undefined} - */ - #videoDialog; - /** - * @type {number | undefined} - */ - #subtitleTrackIndexToSetOnPlaying; - /** - * @type {number | null} - */ - #audioTrackIndexToSetOnPlaying; - /** - * @type {boolean | undefined} - */ - #showTrackOffset; - /** - * @type {number | undefined} - */ - #currentTrackOffset; - /** - * @type {string[] | undefined} - */ - #supportedFeatures; - /** - * @type {string | undefined} - */ - #currentSrc; - /** - * @type {boolean | undefined} - */ - #started; - /** - * @type {boolean | undefined} - */ - #timeUpdated; - /** - * @type {number | null | undefined} - */ - #currentTime; - /** - * @private (used in other files) - * @type {any | undefined} - */ - _currentPlayOptions; - /** - * @type {any | undefined} - */ - #lastProfile; - /** - * @type {number | undefined} - */ - #duration; - /** - * @type {boolean} - */ - #paused = false; - /** - * @type {int} - */ - #volume = 100; - /** - * @type {boolean} - */ - #muted = false; - /** - * @type {float} - */ - #playRate = 1; - #api = undefined; - - constructor() { - if (browser.edgeUwp) { - this.name = 'Windows Video Player'; - } else { - this.name = 'Html Video Player'; - } - } - - currentSrc() { - return this.#currentSrc; - } - - async ensureApi() { - if (!this.#api) { - this.#api = await getApi(); - } - } - - async play(options) { - this.#started = false; - this.#timeUpdated = false; - this.#currentTime = null; - - this.resetSubtitleOffset(); - loading.show(); - await this.ensureApi(); - this.#api.power.setScreensaverEnabled(false); - const elem = await this.createMediaElement(options); - return await this.setCurrentSrc(elem, options); - } - - /** - * @private - */ - getSubtitleParam() { - const options = this._currentPlayOptions; - - if (this.#subtitleTrackIndexToSetOnPlaying != null && this.#subtitleTrackIndexToSetOnPlaying >= 0) { - const initialSubtitleStream = options.mediaSource.MediaStreams[this.#subtitleTrackIndexToSetOnPlaying]; - if (!initialSubtitleStream || initialSubtitleStream.DeliveryMethod === 'Encode') { - this.#subtitleTrackIndexToSetOnPlaying = -1; - } else if (initialSubtitleStream.DeliveryMethod === 'External') { - return '#,' + initialSubtitleStream.DeliveryUrl; - } - } - - if (this.#subtitleTrackIndexToSetOnPlaying == -1 || this.#subtitleTrackIndexToSetOnPlaying == null) { - return ''; - } - - return '#' + this.#subtitleTrackIndexToSetOnPlaying; - } - - tryGetFramerate(options) { - if (options.mediaSource && options.mediaSource.MediaStreams) { - for (const stream of options.mediaSource.MediaStreams) { - if (stream.Type == 'Video') { - return stream.RealFrameRate || stream.AverageFrameRate || null; - } - } - } - } - - /** - * @private - */ - setCurrentSrc(elem, options) { - return new Promise((resolve) => { - const val = options.url; - this.#currentSrc = val; - console.debug(`playing url: ${val}`); - - // Convert to seconds - const ms = (options.playerStartPositionTicks || 0) / 10000; - this._currentPlayOptions = options; - this.#subtitleTrackIndexToSetOnPlaying = options.mediaSource.DefaultSubtitleStreamIndex == null ? -1 : options.mediaSource.DefaultSubtitleStreamIndex; - this.#audioTrackIndexToSetOnPlaying = options.playMethod === 'Transcode' ? null : options.mediaSource.DefaultAudioStreamIndex; - - const streamdata = {type: 'video', headers: {'User-Agent': 'JellyfinMediaPlayer'}, media: {}}; - const fps = this.tryGetFramerate(options); - if (fps) { - streamdata.frameRate = fps; - } - - const player = this.#api.player; - player.load(val, - { startMilliseconds: ms, autoplay: true }, - streamdata, - (this.#audioTrackIndexToSetOnPlaying != null) - ? '#' + this.#audioTrackIndexToSetOnPlaying : '#1', - this.getSubtitleParam(), - resolve); - }); - } - - async setSubtitleStreamIndex(index) { - await this.ensureApi(); - this.#subtitleTrackIndexToSetOnPlaying = index; - this.#api.player.setSubtitleStream(this.getSubtitleParam()); - } - - async resetSubtitleOffset() { - await this.ensureApi(); - this.#currentTrackOffset = 0; - this.#showTrackOffset = false; - this.#api.player.setSubtitleDelay(0); - } - - enableShowingSubtitleOffset() { - this.#showTrackOffset = true; - } - - disableShowingSubtitleOffset() { - this.#showTrackOffset = false; - } - - isShowingSubtitleOffsetEnabled() { - return this.#showTrackOffset; - } - - async setSubtitleOffset(offset) { - await this.ensureApi(); - const offsetValue = parseFloat(offset); - this.#currentTrackOffset = offsetValue; - this.#api.player.setSubtitleDelay(offset); - } - - getSubtitleOffset() { - return this.#currentTrackOffset; - } - - /** - * @private - */ - isAudioStreamSupported() { - return true; - } - - /** - * @private - */ - getSupportedAudioStreams() { - const profile = this.#lastProfile; - - return getMediaStreamAudioTracks(this._currentPlayOptions.mediaSource).filter((stream) => { - return this.isAudioStreamSupported(stream, profile); - }); - } - - async setAudioStreamIndex(index) { - await this.ensureApi(); - this.#audioTrackIndexToSetOnPlaying = index; - const streams = this.getSupportedAudioStreams(); - - if (streams.length < 2) { - // If there's only one supported stream then trust that the player will handle it on it's own - return; - } - - this.#api.player.setAudioStream(index != -1 ? '#' + index : ''); - } - - onEndedInternal() { - const stopInfo = { - src: this.#currentSrc - }; - - Events.trigger(this, 'stopped', [stopInfo]); - - this.#currentTime = null; - this.#currentSrc = null; - this._currentPlayOptions = null; - } - - async stop(destroyPlayer) { - await this.ensureApi(); - this.#api.player.stop(); - this.#api.power.setScreensaverEnabled(true); - - this.onEndedInternal(); - - if (destroyPlayer) { - this.destroy(); - } - return; - } - - async destroy() { - await this.ensureApi(); - this.#api.player.stop(); - this.#api.power.setScreensaverEnabled(true); - - appRouter.setTransparency('none'); - document.body.classList.remove('hide-scroll'); - - const player = this.#api.player; - player.playing.disconnect(this.onPlaying); - player.positionUpdate.disconnect(this.onTimeUpdate); - player.finished.disconnect(this.onEnded); - this.#duration = undefined; - player.updateDuration.disconnect(this.onDuration); - player.error.disconnect(this.onError); - player.paused.disconnect(this.onPause); - - const dlg = this.#videoDialog; - if (dlg) { - this.#videoDialog = null; - dlg.parentNode.removeChild(dlg); - } - - if (Screenfull.isEnabled) { - Screenfull.exit(); - } else { - // iOS Safari - if (document.webkitIsFullScreen && document.webkitCancelFullscreen) { - document.webkitCancelFullscreen(); - } - } - } - - /** - * @private - */ - onEnded = () => { - this.onEndedInternal(); - }; - - /** - * @private - */ - onTimeUpdate = (time) => { - if (time && !this.#timeUpdated) { - this.#timeUpdated = true; - } - - this.#currentTime = time; - Events.trigger(this, 'timeupdate'); - }; - - /** - * @private - */ - onNavigatedToOsd = () => { - const dlg = this.#videoDialog; - if (dlg) { - dlg.classList.remove('videoPlayerContainer-onTop'); - } - }; - - /** - * @private - */ - onPlaying = () => { - if (!this.#started) { - this.#started = true; - - loading.hide(); - - const volume = getSavedVolume() * 100; - if (volume != this.#volume) { - this.setVolume(volume, false); - } - - this.setPlaybackRate(1); - this.setMute(false); - - if (this._currentPlayOptions.fullscreen) { - appRouter.showVideoOsd().then(this.onNavigatedToOsd); - } else { - appRouter.setTransparency('backdrop'); - this.#videoDialog.classList.remove('videoPlayerContainer-onTop'); - } - } - - if (this.#paused) { - this.#paused = false; - Events.trigger(this, 'unpause'); - } - - Events.trigger(this, 'playing'); - }; - - /** - * @private - */ - onPause = () => { - this.#paused = true; - // For Syncplay ready notification - Events.trigger(this, 'pause'); - }; - - onWaiting = () => { - Events.trigger(this, 'waiting'); - }; - - /** - * @private - * @param e {Event} The event received from the `