jellyfish-web/src/plugins/htmlAudioPlayer/plugin.js

514 lines
15 KiB
JavaScript
Raw Normal View History

2020-08-16 20:24:45 +02:00
import browser from '../../scripts/browser';
import { appHost } from '../../components/apphost';
import * as htmlMediaHelper from '../../components/htmlMediaHelper';
2020-10-18 13:53:12 +01:00
import profileBuilder from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
2023-03-08 11:03:48 -05:00
import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts';
function getDefaultProfile() {
2020-10-18 13:53:12 +01:00
return profileBuilder({});
}
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 - 0.15);
console.debug('fading volume to ' + newVolume);
elem.volume = newVolume;
if (newVolume <= 0) {
instance._isFadingOut = false;
return Promise.resolve();
2018-10-23 01:05:09 +03:00
}
return new Promise(function (resolve, reject) {
cancelFadeTimeout();
fadeTimeout = setTimeout(function () {
fade(instance, elem, newVolume).then(resolve, reject);
}, 100);
});
}
function cancelFadeTimeout() {
const timeout = fadeTimeout;
if (timeout) {
clearTimeout(timeout);
fadeTimeout = null;
2018-10-23 01:05:09 +03:00
}
}
2018-10-23 01:05:09 +03:00
function supportsFade() {
// Not working on tizen.
// We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive
return !browser.tv;
}
function requireHlsPlayer(callback) {
2020-08-16 20:24:45 +02:00
import('hls.js').then(({ default: hls }) => {
2023-03-06 10:02:38 +01:00
hls.DefaultConfig.lowLatencyMode = false;
hls.DefaultConfig.backBufferLength = Infinity;
hls.DefaultConfig.liveBackBufferLength = 90;
window.Hls = hls;
callback();
});
}
2018-10-23 01:05:09 +03:00
function enableHlsPlayer(url, item, mediaSource, mediaType) {
if (!htmlMediaHelper.enableHlsJsPlayer(mediaSource.RunTimeTicks, mediaType)) {
return Promise.reject();
2018-10-23 01:05:09 +03:00
}
if (url.indexOf('.m3u8') !== -1) {
return Promise.resolve();
}
// issue head request to get content type
return new Promise(function (resolve, reject) {
2020-08-16 20:24:45 +02:00
import('../../components/fetchhelper').then((fetchHelper) => {
fetchHelper.ajax({
url: url,
type: 'HEAD'
}).then(function (response) {
const contentType = (response.headers.get('Content-Type') || '').toLowerCase();
if (contentType === 'application/vnd.apple.mpegurl' || contentType === 'application/x-mpegurl') {
resolve();
} else {
reject();
}
}, reject);
});
});
}
2018-10-23 01:05:09 +03:00
class HtmlAudioPlayer {
constructor() {
const self = this;
self.name = 'Html Audio Player';
2023-03-08 11:03:48 -05:00
self.type = PluginType.MediaPlayer;
self.id = 'htmlaudioplayer';
// Let any players created by plugins take priority
self.priority = 1;
self.play = function (options) {
self._started = false;
self._timeUpdated = false;
self._currentTime = null;
const elem = createMediaElement();
return setCurrentSrc(elem, options);
};
2018-10-23 01:05:09 +03:00
function setCurrentSrc(elem, options) {
unBindEvents(elem);
bindEvents(elem);
let val = options.url;
2020-02-16 03:44:43 +01:00
console.debug('playing url: ' + val);
// Convert to seconds
const seconds = (options.playerStartPositionTicks || 0) / 10000000;
if (seconds) {
val += '#t=' + seconds;
}
htmlMediaHelper.destroyHlsPlayer(self);
self._currentPlayOptions = options;
const crossOrigin = htmlMediaHelper.getCrossOriginValue(options.mediaSource);
if (crossOrigin) {
elem.crossOrigin = crossOrigin;
}
return enableHlsPlayer(val, options.item, options.mediaSource, 'Audio').then(function () {
return new Promise(function (resolve, reject) {
requireHlsPlayer(async () => {
const includeCorsCredentials = await getIncludeCorsCredentials();
const hls = new Hls({
manifestLoadingTimeOut: 20000,
2021-01-26 22:20:12 -05:00
xhrSetup: function (xhr) {
xhr.withCredentials = includeCorsCredentials;
}
2018-10-23 01:05:09 +03:00
});
hls.loadSource(val);
hls.attachMedia(elem);
htmlMediaHelper.bindEventsToHlsPlayer(self, hls, elem, onError, resolve, reject);
self._hlsPlayer = hls;
self._currentSrc = val;
});
});
}, async () => {
elem.autoplay = true;
const includeCorsCredentials = await getIncludeCorsCredentials();
if (includeCorsCredentials) {
// Safari will not send cookies without this
elem.crossOrigin = 'use-credentials';
}
return htmlMediaHelper.applySrc(elem, val, options).then(function () {
self._currentSrc = val;
return htmlMediaHelper.playWithPromise(elem, onError);
});
});
2018-10-23 01:05:09 +03:00
}
function bindEvents(elem) {
elem.addEventListener('timeupdate', onTimeUpdate);
elem.addEventListener('ended', onEnded);
elem.addEventListener('volumechange', onVolumeChange);
elem.addEventListener('pause', onPause);
elem.addEventListener('playing', onPlaying);
elem.addEventListener('play', onPlay);
2020-04-01 17:53:14 +02:00
elem.addEventListener('waiting', onWaiting);
2018-10-23 01:05:09 +03:00
}
function unBindEvents(elem) {
elem.removeEventListener('timeupdate', onTimeUpdate);
elem.removeEventListener('ended', onEnded);
elem.removeEventListener('volumechange', onVolumeChange);
elem.removeEventListener('pause', onPause);
elem.removeEventListener('playing', onPlaying);
elem.removeEventListener('play', onPlay);
2020-04-01 17:53:14 +02:00
elem.removeEventListener('waiting', onWaiting);
2021-09-12 01:28:56 +03:00
elem.removeEventListener('error', onError); // bound in htmlMediaHelper
2018-10-23 01:05:09 +03:00
}
self.stop = function (destroyPlayer) {
cancelFadeTimeout();
const elem = self._mediaElement;
const src = self._currentSrc;
if (elem && src) {
if (!destroyPlayer || !supportsFade()) {
elem.pause();
htmlMediaHelper.onEndedInternal(self, elem, onError);
if (destroyPlayer) {
self.destroy();
}
return Promise.resolve();
}
const originalVolume = elem.volume;
return fade(self, elem, elem.volume).then(function () {
elem.pause();
elem.volume = originalVolume;
htmlMediaHelper.onEndedInternal(self, elem, onError);
if (destroyPlayer) {
self.destroy();
}
});
}
return Promise.resolve();
};
self.destroy = function () {
unBindEvents(self._mediaElement);
2021-09-17 01:20:58 +03:00
htmlMediaHelper.resetSrc(self._mediaElement);
};
2018-10-23 01:05:09 +03:00
function createMediaElement() {
let elem = self._mediaElement;
if (elem) {
return elem;
}
elem = document.querySelector('.mediaPlayerAudio');
if (!elem) {
elem = document.createElement('audio');
elem.classList.add('mediaPlayerAudio');
elem.classList.add('hide');
document.body.appendChild(elem);
}
elem.volume = htmlMediaHelper.getSavedVolume();
self._mediaElement = elem;
return elem;
2018-10-23 01:05:09 +03:00
}
function onEnded() {
htmlMediaHelper.onEndedInternal(self, this, onError);
2018-10-23 01:05:09 +03:00
}
function onTimeUpdate() {
// Get the player position + the transcoding offset
const time = this.currentTime;
// Don't trigger events after user stop
if (!self._isFadingOut) {
self._currentTime = time;
2020-09-08 02:05:02 -04:00
Events.trigger(self, 'timeupdate');
}
2018-10-23 01:05:09 +03:00
}
function onVolumeChange() {
if (!self._isFadingOut) {
htmlMediaHelper.saveVolume(this.volume);
2020-09-08 02:05:02 -04:00
Events.trigger(self, 'volumechange');
}
2018-10-23 01:05:09 +03:00
}
function onPlaying(e) {
if (!self._started) {
self._started = true;
this.removeAttribute('controls');
htmlMediaHelper.seekOnPlaybackStart(self, e.target, self._currentPlayOptions.playerStartPositionTicks);
}
2020-09-08 02:05:02 -04:00
Events.trigger(self, 'playing');
2018-10-23 01:05:09 +03:00
}
2021-01-26 22:20:12 -05:00
function onPlay() {
2020-09-08 02:05:02 -04:00
Events.trigger(self, 'unpause');
2018-10-23 01:05:09 +03:00
}
function onPause() {
2020-09-08 02:05:02 -04:00
Events.trigger(self, 'pause');
2018-10-23 01:05:09 +03:00
}
2020-04-01 17:53:14 +02:00
function onWaiting() {
2020-09-08 02:05:02 -04:00
Events.trigger(self, 'waiting');
2020-04-01 17:53:14 +02:00
}
2018-10-23 01:05:09 +03:00
function onError() {
const errorCode = this.error ? (this.error.code || 0) : 0;
const errorMessage = this.error ? (this.error.message || '') : '';
console.error('media element error: ' + errorCode.toString() + ' ' + errorMessage);
let type;
2018-10-23 01:05:09 +03:00
switch (errorCode) {
case 1:
// MEDIA_ERR_ABORTED
// This will trigger when changing media while something is playing
2018-10-23 01:05:09 +03:00
return;
case 2:
// MEDIA_ERR_NETWORK
type = 'network';
2018-10-23 01:05:09 +03:00
break;
case 3:
// MEDIA_ERR_DECODE
if (self._hlsPlayer) {
htmlMediaHelper.handleHlsJsMediaError(self);
return;
} else {
type = 'mediadecodeerror';
}
2018-10-23 01:05:09 +03:00
break;
case 4:
// MEDIA_ERR_SRC_NOT_SUPPORTED
type = 'medianotsupported';
2018-10-23 01:05:09 +03:00
break;
default:
// seeing cases where Edge is firing error events with no error code
// example is start playing something, then immediately change src to something else
return;
2018-10-23 01:05:09 +03:00
}
htmlMediaHelper.onErrorInternal(self, type);
2018-10-23 01:05:09 +03:00
}
}
currentSrc() {
return this._currentSrc;
}
canPlayMediaType(mediaType) {
return (mediaType || '').toLowerCase() === 'audio';
}
getDeviceProfile(item) {
if (appHost.getDeviceProfile) {
return appHost.getDeviceProfile(item);
}
return getDefaultProfile();
}
// Save this for when playback stops, because querying the time at that point might return 0
currentTime(val) {
const mediaElement = this._mediaElement;
2018-10-23 01:05:09 +03:00
if (mediaElement) {
if (val != null) {
mediaElement.currentTime = val / 1000;
return;
}
const currentTime = this._currentTime;
if (currentTime) {
return currentTime * 1000;
}
return (mediaElement.currentTime || 0) * 1000;
2018-10-23 01:05:09 +03:00
}
}
2021-01-26 22:20:12 -05:00
duration() {
const mediaElement = this._mediaElement;
2018-10-23 01:05:09 +03:00
if (mediaElement) {
const duration = mediaElement.duration;
if (htmlMediaHelper.isValidDuration(duration)) {
return duration * 1000;
}
2018-10-23 01:05:09 +03:00
}
return null;
}
seekable() {
const mediaElement = this._mediaElement;
2018-10-23 01:05:09 +03:00
if (mediaElement) {
const seekable = mediaElement.seekable;
2018-10-23 01:05:09 +03:00
if (seekable && seekable.length) {
let start = seekable.start(0);
let end = seekable.end(0);
if (!htmlMediaHelper.isValidDuration(start)) {
start = 0;
}
if (!htmlMediaHelper.isValidDuration(end)) {
end = 0;
}
return (end - start) > 0;
2018-10-23 01:05:09 +03:00
}
return false;
2018-10-23 01:05:09 +03:00
}
}
getBufferedRanges() {
const mediaElement = this._mediaElement;
if (mediaElement) {
return htmlMediaHelper.getBufferedRanges(this, mediaElement);
}
return [];
}
pause() {
const mediaElement = this._mediaElement;
if (mediaElement) {
mediaElement.pause();
}
}
// This is a retry after error
resume() {
2022-10-05 15:31:15 -04:00
this.unpause();
}
unpause() {
const mediaElement = this._mediaElement;
if (mediaElement) {
mediaElement.play();
}
}
paused() {
const mediaElement = this._mediaElement;
if (mediaElement) {
return mediaElement.paused;
}
return false;
}
setPlaybackRate(value) {
const mediaElement = this._mediaElement;
2020-04-01 17:53:14 +02:00
if (mediaElement) {
mediaElement.playbackRate = value;
}
}
2020-04-01 17:53:14 +02:00
getPlaybackRate() {
const mediaElement = this._mediaElement;
2020-04-01 17:53:14 +02:00
if (mediaElement) {
return mediaElement.playbackRate;
}
return null;
}
2020-04-01 17:53:14 +02:00
setVolume(val) {
const mediaElement = this._mediaElement;
if (mediaElement) {
2021-08-10 16:16:52 -07:00
mediaElement.volume = Math.pow(val / 100, 3);
}
}
getVolume() {
const mediaElement = this._mediaElement;
if (mediaElement) {
2021-08-10 16:16:52 -07:00
return Math.min(Math.round(Math.pow(mediaElement.volume, 1 / 3) * 100), 100);
}
}
volumeUp() {
this.setVolume(Math.min(this.getVolume() + 2, 100));
}
volumeDown() {
this.setVolume(Math.max(this.getVolume() - 2, 0));
}
setMute(mute) {
const mediaElement = this._mediaElement;
if (mediaElement) {
mediaElement.muted = mute;
}
}
isMuted() {
const mediaElement = this._mediaElement;
if (mediaElement) {
return mediaElement.muted;
}
return false;
}
2020-04-01 17:53:14 +02:00
supports(feature) {
2020-04-01 17:53:14 +02:00
if (!supportedFeatures) {
supportedFeatures = getSupportedFeatures();
}
return supportedFeatures.indexOf(feature) !== -1;
}
}
let supportedFeatures;
function getSupportedFeatures() {
const list = [];
const audio = document.createElement('audio');
if (typeof audio.playbackRate === 'number') {
list.push('PlaybackRate');
}
return list;
}
2020-04-01 17:53:14 +02:00
export default HtmlAudioPlayer;