1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00
fcast/receivers/common/web/player/Player.ts

350 lines
11 KiB
TypeScript
Raw Normal View History

2025-06-10 14:23:06 -05:00
import { PlayMessage } from 'common/Packets';
2024-12-09 00:56:55 -06:00
import dashjs from 'modules/dashjs';
import Hls from 'modules/hls.js';
2024-11-04 09:17:20 -06:00
2025-05-01 10:37:21 -05:00
const logger = window.targetAPI.logger;
2024-11-04 09:17:20 -06:00
export enum PlayerType {
Html,
Dash,
Hls,
}
export class Player {
2025-06-10 14:23:06 -05:00
private player: HTMLVideoElement;
private playMessage: PlayMessage;
private source: string;
2025-06-10 14:23:06 -05:00
// Todo: use a common event handler interface instead of exposing internal players
public playerType: PlayerType;
2025-06-10 14:23:06 -05:00
public dashPlayer: dashjs.MediaPlayerClass = null;
public hlsPlayer: Hls = null;
2024-11-04 09:17:20 -06:00
2025-06-10 14:23:06 -05:00
constructor(player: HTMLVideoElement, message: PlayMessage) {
2024-11-04 09:17:20 -06:00
this.player = player;
2025-06-10 14:23:06 -05:00
this.playMessage = message;
if (message.container === 'application/dash+xml') {
this.playerType = PlayerType.Dash;
this.source = message.content ? message.content : message.url;
this.dashPlayer = dashjs.MediaPlayer().create();
this.dashPlayer.extend("RequestModifier", () => {
return {
modifyRequestHeader: function (xhr) {
if (message.headers) {
for (const [key, val] of Object.entries(message.headers)) {
xhr.setRequestHeader(key, val);
}
}
return xhr;
}
};
}, true);
} else if ((message.container === 'application/vnd.apple.mpegurl' || message.container === 'application/x-mpegURL') && !player.canPlayType(message.container)) {
this.playerType = PlayerType.Hls;
this.source = message.url;
const config = {
xhrSetup: function (xhr: XMLHttpRequest) {
if (message.headers) {
for (const [key, val] of Object.entries(message.headers)) {
xhr.setRequestHeader(key, val);
}
}
},
};
this.hlsPlayer = new Hls(config);
} else {
this.playerType = PlayerType.Html;
this.source = message.url;
}
2024-11-04 09:17:20 -06:00
}
2025-06-10 14:23:06 -05:00
public destroy() {
2024-11-04 09:17:20 -06:00
switch (this.playerType) {
case PlayerType.Dash:
try {
2025-06-10 14:23:06 -05:00
this.dashPlayer.destroy();
2024-11-04 09:17:20 -06:00
} catch (e) {
2025-05-01 10:37:21 -05:00
logger.warn("Failed to destroy dash player", e);
2024-11-04 09:17:20 -06:00
}
2025-06-10 14:23:06 -05:00
2024-11-04 09:17:20 -06:00
break;
case PlayerType.Hls:
// HLS also uses html player
try {
this.hlsPlayer.destroy();
} catch (e) {
2025-05-01 10:37:21 -05:00
logger.warn("Failed to destroy hls player", e);
2024-11-04 09:17:20 -06:00
}
2025-06-10 14:23:06 -05:00
// fallthrough
2024-11-04 09:17:20 -06:00
case PlayerType.Html: {
2025-06-10 14:23:06 -05:00
this.player.src = "";
// this.player.onerror = null;
this.player.onloadedmetadata = null;
this.player.ontimeupdate = null;
this.player.onplay = null;
this.player.onpause = null;
this.player.onended = null;
this.player.ontimeupdate = null;
this.player.onratechange = null;
this.player.onvolumechange = null;
2024-11-04 09:17:20 -06:00
break;
}
default:
break;
}
2025-06-10 14:23:06 -05:00
this.player = null;
this.playerType = null;
this.dashPlayer = null;
this.hlsPlayer = null;
this.playMessage = null;
this.source = null;
2024-11-04 09:17:20 -06:00
}
2025-06-10 14:23:06 -05:00
/**
* Load media specified in the PlayMessage provided on object initialization
*/
public load() {
if (this.playerType === PlayerType.Dash) {
if (this.playMessage.content) {
this.dashPlayer.initialize(this.player, `data:${this.playMessage.container};base64,` + window.btoa(this.playMessage.content), true, this.playMessage.time);
// dashPlayer.initialize(videoElement, "https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/elephants_dream_480p_heaac5_1_https.mpd", true);
} else {
// value.url = 'https://dash.akamaized.net/akamai/bbb_30fps/bbb_30fps.mpd';
this.dashPlayer.initialize(this.player, this.playMessage.url, true, this.playMessage.time);
}
} else if (this.playerType === PlayerType.Hls) {
// value.url = "https://devstreaming-cdn.apple.com/videos/streaming/examples/adv_dv_atmos/main.m3u8?ref=developerinsider.co";
this.hlsPlayer.loadSource(this.playMessage.url);
this.hlsPlayer.attachMedia(this.player);
// hlsPlayer.subtitleDisplay = true;
} else { // HTML
this.player.src = this.playMessage.url;
this.player.load();
}
}
public play() {
logger.info("Player: play");
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
this.dashPlayer.play();
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
this.player.play();
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public isPaused(): boolean {
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
return this.dashPlayer.isPaused();
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
return this.player.paused;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public pause() {
logger.info("Player: pause");
if (this.playerType === PlayerType.Dash) {
this.dashPlayer.pause();
} else { // HLS, HTML
this.player.pause();
}
}
public stop() {
const playbackRate = this.getPlaybackRate();
const volume = this.getVolume();
if (this.playerType === PlayerType.Dash) {
if (this.playMessage.content) {
this.dashPlayer.initialize(this.player, `data:${this.playMessage.container};base64,` + window.btoa(this.playMessage.content), false);
} else {
this.dashPlayer.initialize(this.player, this.playMessage.url, false);
}
} else if (this.playerType === PlayerType.Hls) {
this.hlsPlayer.loadSource(this.source);
} else {
this.player.load();
}
this.setPlaybackRate(playbackRate);
this.setVolume(volume);
}
public getVolume(): number {
if (this.playerType === PlayerType.Dash) {
return this.dashPlayer.getVolume();
} else { // HLS, HTML
return this.player.volume;
}
}
public setVolume(value: number) {
// logger.info(`Player: setVolume ${value}`);
2024-11-04 09:17:20 -06:00
const sanitizedVolume = Math.min(1.0, Math.max(0.0, value));
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
this.dashPlayer.setVolume(sanitizedVolume);
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
this.player.volume = sanitizedVolume;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public isMuted(): boolean {
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
return this.dashPlayer.isMuted();
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
return this.player.muted;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public setMute(value: boolean) {
2025-05-01 10:37:21 -05:00
logger.info(`Player: setMute ${value}`);
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
this.dashPlayer.setMute(value);
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
this.player.muted = value;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public getPlaybackRate(): number {
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
return this.dashPlayer.getPlaybackRate();
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
return this.player.playbackRate;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public setPlaybackRate(value: number) {
2025-05-01 10:37:21 -05:00
logger.info(`Player: setPlaybackRate ${value}`);
2024-11-04 09:17:20 -06:00
const sanitizedSpeed = Math.min(16.0, Math.max(0.0, value));
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
this.dashPlayer.setPlaybackRate(sanitizedSpeed);
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
this.player.playbackRate = sanitizedSpeed;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public getDuration(): number {
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
return isFinite(this.dashPlayer.duration()) ? this.dashPlayer.duration() : 0;
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
return isFinite(this.player.duration) ? this.player.duration : 0;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public getCurrentTime(): number {
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
return this.dashPlayer.time();
2024-11-04 09:17:20 -06:00
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
return this.player.currentTime;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public setCurrentTime(value: number) {
2025-05-01 10:37:21 -05:00
// logger.info(`Player: setCurrentTime ${value}`);
2024-11-04 09:17:20 -06:00
const sanitizedTime = Math.min(this.getDuration(), Math.max(0.0, value));
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
this.dashPlayer.seek(sanitizedTime);
2024-11-04 09:17:20 -06:00
2025-06-10 14:23:06 -05:00
if (!this.dashPlayer.isSeeking()) {
this.dashPlayer.seek(sanitizedTime);
2024-11-04 09:17:20 -06:00
}
} else { // HLS, HTML
2025-06-10 14:23:06 -05:00
this.player.currentTime = sanitizedTime;
2024-11-04 09:17:20 -06:00
}
}
2025-06-10 14:23:06 -05:00
public getSource(): string {
return this.source;
2024-11-04 09:17:20 -06:00
}
2025-06-10 14:23:06 -05:00
public getAutoplay(): boolean {
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
return this.dashPlayer.getAutoPlay();
} else { // HLS, HTML
return this.player.autoplay;
}
}
2024-11-04 09:17:20 -06:00
2025-06-10 14:23:06 -05:00
public setAutoPlay(value: boolean) {
if (this.playerType === PlayerType.Dash) {
return this.dashPlayer.setAutoPlay(value);
} else { // HLS, HTML
return this.player.autoplay = value;
}
}
public getBufferLength(): number {
if (this.playerType === PlayerType.Dash) {
let dashBufferLength = this.dashPlayer.getBufferLength("video")
?? this.dashPlayer.getBufferLength("audio")
?? this.dashPlayer.getBufferLength("text")
?? this.dashPlayer.getBufferLength("image")
2024-11-04 09:17:20 -06:00
?? 0;
if (Number.isNaN(dashBufferLength))
dashBufferLength = 0;
2025-06-10 14:23:06 -05:00
dashBufferLength += this.dashPlayer.time();
2024-11-04 09:17:20 -06:00
return dashBufferLength;
} else { // HLS, HTML
let maxBuffer = 0;
2025-06-10 14:23:06 -05:00
if (this.player.buffered) {
for (let i = 0; i < this.player.buffered.length; i++) {
const start = this.player.buffered.start(i);
const end = this.player.buffered.end(i);
2024-11-04 09:17:20 -06:00
2025-06-10 14:23:06 -05:00
if (this.player.currentTime >= start && this.player.currentTime <= end) {
2024-11-04 09:17:20 -06:00
maxBuffer = end;
}
}
}
return maxBuffer;
}
}
2025-06-10 14:23:06 -05:00
public isCaptionsSupported(): boolean {
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
return this.dashPlayer.getTracksFor('text').length > 0;
} else if (this.playerType === PlayerType.Hls) {
return this.hlsPlayer.allSubtitleTracks.length > 0;
} else {
return false; // HTML captions not currently supported
}
}
2025-06-10 14:23:06 -05:00
public isCaptionsEnabled(): boolean {
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
return this.dashPlayer.isTextEnabled();
2024-11-04 09:17:20 -06:00
} else if (this.playerType === PlayerType.Hls) {
return this.hlsPlayer.subtitleDisplay;
} else {
return false; // HTML captions not currently supported
}
}
2025-06-10 14:23:06 -05:00
public enableCaptions(enable: boolean) {
2024-11-04 09:17:20 -06:00
if (this.playerType === PlayerType.Dash) {
2025-06-10 14:23:06 -05:00
this.dashPlayer.enableText(enable);
2024-11-04 09:17:20 -06:00
} else if (this.playerType === PlayerType.Hls) {
this.hlsPlayer.subtitleDisplay = enable;
}
// HTML captions not currently supported
}
}