owncast/webroot/js/components/player.js

170 lines
4.6 KiB
JavaScript
Raw Normal View History

// https://docs.videojs.com/player
HLS video handling/storage/state refactor (#151) * WIP with new transcoder progress monitor * A whole different WIP in progress monitoring via local PUTs * Use an actual hls playlist parser to rewrite master playlist * Cleanup * Private vs public path for thumbnail generation * Allow each storage provider to make decisions of how to store different types of files * Simplify inbound file writes * Revert * Split out set stream as connected/disconnected state methods * Update videojs * Add comment about the hls handler * Rework of the offline stream state. For #85 * Delete old unreferenced video segment files from disk * Cleanup all segments and revert to a completely offline state after 5min * Stop thumbnail generation on stream stop. Copy logo to thumbnail on cleanup. * Update transcoder test * Add comment * Return http 200 on success to transcoder. Tweak how files are written to disk * Force pixel color format in transcoder * Add debugging info for S3 transfers. Add default ACL. * Fix cleanup timer * Reset session stats when we cleanup the session. * Put log file back * Update test * File should not be a part of this commit * Add centralized shared performance timer for use anywhere * Post-rebase cleanup * Support returning nil from storage provider save * Updates to reflect package changes + other updates in master * Fix storage providers being overwritten * Do not return pointer in save. Support cache headers with S3 providers * Split out videojs + vhs and point to specific working versions of them * Bump vjs and vhs versions * Fix test * Remove unused * Update upload warning message * No longer valid comment * Pin videojs and vhs versions
2020-10-15 00:07:38 +03:00
import videojs from '/js/web_modules/videojs/core.js';
import '/js/web_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js';
import { getLocalStorage, setLocalStorage } from '../utils/helpers.js';
import { PLAYER_VOLUME } from '../utils/constants.js';
const VIDEO_ID = 'video';
Merge of emoji + autolink + embed + etc (#108) * Add an emoji picker to chat * Update to the custom emoji picker and add first pass at using custom emoji in text box * Add custom emoji endpoint and use it in the app * Position the emoji picker * Handle events from the text input * pair down the number of party parrots * Size emoji in chat and input * Add new custom emoji * Add OMQ stickers as custom emoji * Show custom category for emoji picker by default * update omq emojis * Document basic supported markdown syntax. Closes #95 * Websocket refactor: Pull it out of the UI and support callbacks (#104) * Websocket refactor: Pull it out of the UI and support listeners * Changes required for Safari to be happy with modules * Move to explicit ad-hoc callback registration * Add an emoji picker to chat * Update to the custom emoji picker and add first pass at using custom emoji in text box * Handle events from the text input * Rebuild autolinking + embed handling for #93 * Re-enable disabling chat * Document basic supported markdown syntax. Closes #95 * Document basic supported markdown syntax. Closes #95 * Add an emoji picker to chat * Merge emoji and embeds. * Merge emoji + embed branches. Rework autolink +embeds. WIP for username highlighting for #100 * More updates to chat text formatting/embedding/linking * Fix username autocomplete to work with div instead of form elements * Post-rebase fixes + tweaks * Disable text input by setting contentEditable = false * Remove test that hardcodes pointing to public test server * Fix re-enable chat with the contentEditable input div * Style and fix the fake placeholder text in the input div * Missing file. Were did it go? * Set a height for instagram embeds * Cleanup Co-authored-by: Ginger Wong <omqmail@gmail.com>
2020-08-13 07:56:41 +03:00
const URL_STREAM = `/hls/stream.m3u8`;
// Video setup
const VIDEO_SRC = {
src: URL_STREAM,
type: 'application/x-mpegURL',
};
const VIDEO_OPTIONS = {
autoplay: false,
liveui: true,
preload: 'auto',
controlBar: {
progressControl: {
seekBar: false,
},
},
html5: {
vhs: {
// used to select the lowest bitrate playlist initially. This helps to decrease playback start time. This setting is false by default.
enableLowInitialPlaylist: true,
},
},
liveTracker: {
trackingThreshold: 0,
},
sources: [VIDEO_SRC],
};
2020-08-21 01:51:11 +03:00
export const POSTER_DEFAULT = `/img/logo.png`;
export const POSTER_THUMB = `/thumbnail.jpg`;
class OwncastPlayer {
constructor() {
window.VIDEOJS_NO_DYNAMIC_STYLE = true; // style override
this.vjsPlayer = null;
this.appPlayerReadyCallback = null;
this.appPlayerPlayingCallback = null;
this.appPlayerEndedCallback = null;
// bind all the things because safari
this.startPlayer = this.startPlayer.bind(this);
this.handleReady = this.handleReady.bind(this);
this.handlePlaying = this.handlePlaying.bind(this);
this.handleVolume = this.handleVolume.bind(this);
this.handleEnded = this.handleEnded.bind(this);
this.handleError = this.handleError.bind(this);
}
init() {
this.vjsPlayer = videojs(VIDEO_ID, VIDEO_OPTIONS);
this.vjsPlayer.beforeRequest = function (options) {
const cachebuster = Math.round(new Date().getTime() / 1000);
options.uri = `${options.uri}?cachebust=${cachebuster}`;
return options;
};
this.addAirplay();
this.vjsPlayer.ready(this.handleReady);
}
setupPlayerCallbacks(callbacks) {
const { onReady, onPlaying, onEnded, onError } = callbacks;
this.appPlayerReadyCallback = onReady;
this.appPlayerPlayingCallback = onPlaying;
this.appPlayerEndedCallback = onEnded;
this.appPlayerErrorCallback = onError;
}
// play
startPlayer() {
this.log('Start playing');
const source = { ...VIDEO_SRC };
2020-10-07 03:01:59 +03:00
this.vjsPlayer.volume(getLocalStorage(PLAYER_VOLUME) || 1);
this.vjsPlayer.src(source);
// this.vjsPlayer.play();
}
handleReady() {
this.log('on Ready');
this.vjsPlayer.on('error', this.handleError);
this.vjsPlayer.on('playing', this.handlePlaying);
this.vjsPlayer.on('volumechange', this.handleVolume);
this.vjsPlayer.on('ended', this.handleEnded);
if (this.appPlayerReadyCallback) {
// start polling
this.appPlayerReadyCallback();
}
}
handleVolume() {
setLocalStorage(PLAYER_VOLUME, this.vjsPlayer.muted() ? 0 : this.vjsPlayer.volume());
}
handlePlaying() {
this.log('on Playing');
if (this.appPlayerPlayingCallback) {
// start polling
this.appPlayerPlayingCallback();
}
}
handleEnded() {
this.log('on Ended');
if (this.appPlayerEndedCallback) {
this.appPlayerEndedCallback();
}
this.setPoster();
}
handleError(e) {
this.log(`on Error: ${JSON.stringify(e)}`);
if (this.appPlayerEndedCallback) {
this.appPlayerEndedCallback();
}
}
setPoster() {
const cachebuster = Math.round(new Date().getTime() / 1000);
const poster = POSTER_THUMB + '?okhi=' + cachebuster;
this.vjsPlayer.poster(poster);
}
log(message) {
2020-09-24 04:18:15 +03:00
// console.log(`>>> Player: ${message}`);
}
addAirplay() {
videojs.hookOnce('setup', function (player) {
if (window.WebKitPlaybackTargetAvailabilityEvent) {
var videoJsButtonClass = videojs.getComponent('Button');
var concreteButtonClass = videojs.extend(videoJsButtonClass, {
2020-08-21 01:29:15 +03:00
// The `init()` method will also work for constructor logic here, but it is
// deprecated. If you provide an `init()` method, it will override the
// `constructor()` method!
constructor: function () {
videoJsButtonClass.call(this, player);
2020-08-21 01:29:15 +03:00
},
handleClick: function () {
const videoElement = document.getElementsByTagName('video')[0];
videoElement.webkitShowPlaybackTargetPicker();
2020-08-21 01:29:15 +03:00
},
});
2020-08-21 01:29:15 +03:00
var concreteButtonInstance = this.vjsPlayer.controlBar.addChild(
new concreteButtonClass()
);
concreteButtonInstance.addClass('vjs-airplay');
}
2020-08-21 01:29:15 +03:00
});
}
}
2020-08-21 01:29:15 +03:00
export { OwncastPlayer };