Files
jellyfin-web/src/plugins/mpvAudioPlayer/plugin.js
2021-04-07 01:40:26 -04:00

379 lines
9.5 KiB
JavaScript

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.mustDestroy = 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'}, frameRate: 0, 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;