diff --git a/web/components/video/owncastplayer.tsx b/web/components/video/owncastplayer.tsx
new file mode 100644
index 000000000..3a2e64334
--- /dev/null
+++ b/web/components/video/owncastplayer.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+
+// This imports the functional component from the previous sample.
+import VideoJS from './player';
+
+export default function OwncastPlayer(props) {
+ const playerRef = React.useRef(null);
+ const { source } = props;
+
+ const videoJsOptions = {
+ autoplay: false,
+ controls: true,
+ responsive: true,
+ fluid: true,
+ 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,
+ experimentalBufferBasedABR: true,
+ useNetworkInformationApi: true,
+ maxPlaylistRetries: 30,
+ },
+ },
+ liveTracker: {
+ trackingThreshold: 0,
+ liveTolerance: 15,
+ },
+ sources: [
+ {
+ src: `${source}/hls/stream.m3u8`,
+ type: 'application/x-mpegURL',
+ },
+ ],
+ };
+
+ const handlePlayerReady = player => {
+ playerRef.current = player;
+
+ // You can handle player events here, for example:
+ player.on('waiting', () => {
+ player.log('player is waiting');
+ });
+
+ player.on('dispose', () => {
+ player.log('player will dispose');
+ });
+ };
+
+ return ;
+}
diff --git a/web/components/video/player.tsx b/web/components/video/player.tsx
new file mode 100644
index 000000000..d150527f4
--- /dev/null
+++ b/web/components/video/player.tsx
@@ -0,0 +1,438 @@
+import React from 'react';
+import videojs from 'video.js';
+
+require('video.js/dist/video-js.css');
+
+// import { getLocalStorage, setLocalStorage } from '../../utils/helpers.js';
+// import { PLAYER_VOLUME, URL_STREAM } from '../../utils/constants.js';
+
+export function VideoJS(props) {
+ const videoRef = React.useRef(null);
+ const playerRef = React.useRef(null);
+ const { options, onReady } = props;
+
+ React.useEffect(() => {
+ // Make sure Video.js player is only initialized once
+ if (!playerRef.current) {
+ const videoElement = videoRef.current;
+
+ // if (!videoElement) return;
+
+ const player = (playerRef.current = videojs(videoElement, options, () => {
+ player.log('player is ready');
+ onReady && onReady(player);
+ }));
+
+ // You can update player in the `else` block here, for example:
+ // } else {
+ player.autoplay(options.autoplay);
+ player.src(options.sources);
+ }
+ }, [options, videoRef]);
+
+ // Dispose the Video.js player when the functional component unmounts
+ React.useEffect(() => {
+ const player = playerRef.current;
+
+ return () => {
+ if (player) {
+ player.dispose();
+ playerRef.current = null;
+ }
+ };
+ }, [playerRef]);
+
+ return (
+
+
+
+ );
+}
+
+export default VideoJS;
+
+// import PlaybackMetrics from '../metrics/playback.js';
+// import LatencyCompensator from './latencyCompensator.js';
+
+// const VIDEO_ID = 'video';
+// const LATENCY_COMPENSATION_ENABLED = 'latencyCompensatorEnabled';
+
+// // 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,
+// experimentalBufferBasedABR: true,
+// useNetworkInformationApi: true,
+// maxPlaylistRetries: 30,
+// },
+// },
+// liveTracker: {
+// trackingThreshold: 0,
+// liveTolerance: 15,
+// },
+// sources: [VIDEO_SRC],
+// };
+
+// export const POSTER_DEFAULT = `/img/logo.png`;
+// export const POSTER_THUMB = `/thumbnail.jpg`;
+
+// export default class MenuSeparator extends VjsMenuItem {
+// constructor(player, options) {
+// super(player, options);
+// }
+
+// createEl(tag = 'button', props = {}, attributes = {}) {
+// const el = super.createEl(tag, props, attributes);
+// el.innerHTML = '
';
+// return el;
+// }
+// }
+
+// class OwncastPlayer {
+// constructor() {
+// window.VIDEOJS_NO_DYNAMIC_STYLE = true; // style override
+
+// this.vjsPlayer = null;
+// this.latencyCompensator = null;
+// this.playbackMetrics = null;
+
+// this.appPlayerReadyCallback = null;
+// this.appPlayerPlayingCallback = null;
+// this.appPlayerEndedCallback = null;
+
+// this.hasStartedPlayback = false;
+// this.latencyCompensatorEnabled = false;
+
+// // 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);
+// this.addQualitySelector = this.addVideoSettingsMenu.bind(this);
+// this.addQualitySelector = this.addVideoSettingsMenu.bind(this);
+// this.toggleLatencyCompensator = this.toggleLatencyCompensator.bind(this);
+// this.startLatencyCompensator = this.startLatencyCompensator.bind(this);
+// this.stopLatencyCompensator = this.stopLatencyCompensator.bind(this);
+// this.qualitySelectionMenu = null;
+// this.latencyCompensatorToggleButton = null;
+// }
+
+// init() {
+// // this.addAirplay();
+// // this.addVideoSettingsMenu();
+
+// // Add a cachebuster param to playlist URLs.
+// videojs.Vhs.xhr.beforeRequest = options => {
+// if (options.uri.match('m3u8')) {
+// const cachebuster = Math.random().toString(16).substr(2, 8);
+// options.uri = `${options.uri}?cachebust=${cachebuster}`;
+// }
+
+// return options;
+// };
+
+// this.vjsPlayer = videojs(VIDEO_ID, VIDEO_OPTIONS);
+
+// 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 };
+
+// try {
+// this.vjsPlayer.volume(getLocalStorage(PLAYER_VOLUME) || 1);
+// } catch (err) {
+// console.warn(err);
+// }
+// this.vjsPlayer.src(source);
+// }
+
+// setupPlaybackMetrics() {
+// // this.playbackMetrics = new PlaybackMetrics(this.vjsPlayer, videojs);
+// }
+
+// setupLatencyCompensator() {
+// const tech = this.vjsPlayer.tech({ IWillNotUseThisInPlugins: true });
+
+// // VHS is required.
+// if (!tech || !tech.vhs) {
+// }
+
+// // const latencyCompensatorEnabledSaved = getLocalStorage(LATENCY_COMPENSATION_ENABLED);
+
+// // if (latencyCompensatorEnabledSaved === 'true' && tech && tech.vhs) {
+// // this.startLatencyCompensator();
+// // } else {
+// // this.stopLatencyCompensator();
+// // }
+// }
+
+// startLatencyCompensator() {
+// // this.latencyCompensator = new LatencyCompensator(this.vjsPlayer);
+// // this.latencyCompensator.enable();
+// // this.latencyCompensatorEnabled = true;
+// // this.setLatencyCompensatorItemTitle('disable minimized latency');
+// }
+
+// stopLatencyCompensator() {
+// // if (this.latencyCompensator) {
+// // this.latencyCompensator.disable();
+// // }
+// // this.LatencyCompensator = null;
+// // this.latencyCompensatorEnabled = false;
+// // this.setLatencyCompensatorItemTitle(
+// // 'enable minimized latency (experimental)',
+// // );
+// }
+
+// handleReady() {
+// console.log('handleReady');
+// this.vjsPlayer.on('error', this.handleError);
+// this.vjsPlayer.on('playing', this.handlePlaying);
+// this.vjsPlayer.on('volumechange', this.handleVolume);
+// this.vjsPlayer.on('ended', this.handleEnded);
+
+// this.vjsPlayer.on('loadeddata', () => {
+// this.setupPlaybackMetrics();
+// this.setupLatencyCompensator();
+// });
+
+// if (this.appPlayerReadyCallback) {
+// // start polling
+// this.appPlayerReadyCallback();
+// }
+
+// this.vjsPlayer.log.level('debug');
+// }
+
+// handleVolume() {
+// setLocalStorage(PLAYER_VOLUME, this.vjsPlayer.muted() ? 0 : this.vjsPlayer.volume());
+// }
+
+// handlePlaying() {
+// if (this.appPlayerPlayingCallback) {
+// // start polling
+// this.appPlayerPlayingCallback();
+// }
+
+// if (this.latencyCompensator && !this.hasStartedPlayback) {
+// this.latencyCompensator.enable();
+// }
+
+// this.hasStartedPlayback = true;
+// }
+
+// handleEnded() {
+// if (this.appPlayerEndedCallback) {
+// this.appPlayerEndedCallback();
+// }
+
+// this.stopLatencyCompensator();
+// }
+
+// handleError(e) {
+// this.log(`on Error: ${JSON.stringify(e)}`);
+// if (this.appPlayerEndedCallback) {
+// this.appPlayerEndedCallback();
+// }
+// }
+
+// toggleLatencyCompensator() {
+// if (this.latencyCompensatorEnabled) {
+// this.stopLatencyCompensator();
+// setLocalStorage(LATENCY_COMPENSATION_ENABLED, false);
+// } else {
+// this.startLatencyCompensator();
+// setLocalStorage(LATENCY_COMPENSATION_ENABLED, true);
+// }
+// }
+
+// setLatencyCompensatorItemTitle(title) {
+// const item = document.querySelector('.latency-toggle-item > .vjs-menu-item-text');
+// if (!item) {
+// return;
+// }
+
+// item.innerHTML = title;
+// }
+
+// log(message) {
+// // console.log(`>>> Player: ${message}`);
+// }
+
+// render(): JSX.Element {
+// return (
+//
+//
+//
+// );
+// }
+
+// async addVideoSettingsMenu() {
+// if (this.qualityMenuButton) {
+// player.controlBar.removeChild(this.qualityMenuButton);
+// }
+
+// videojs.hookOnce('setup', async player => {
+// let qualities = [];
+
+// try {
+// const response = await fetch('http://localhost:8080/api/video/variants');
+// qualities = await response.json();
+// } catch (e) {
+// console.error(e);
+// }
+
+// const MenuItem = videojs.getComponent('MenuItem');
+// const MenuButtonClass = videojs.getComponent('MenuButton');
+
+// const lowLatencyItem = new MenuItem(player, {
+// selectable: true,
+// });
+// lowLatencyItem.setAttribute('class', 'latency-toggle-item');
+// lowLatencyItem.on('click', () => {
+// this.toggleLatencyCompensator();
+// });
+// this.latencyCompensatorToggleButton = lowLatencyItem;
+
+// const separator = new MenuSeparator(player, {
+// selectable: false,
+// });
+
+// const MenuButton = videojs.extend(MenuButtonClass, {
+// // 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() {
+// MenuButtonClass.call(this, player);
+// },
+
+// createItems() {
+// const tech = this.player_.tech({ IWillNotUseThisInPlugins: true });
+
+// const defaultAutoItem = new MenuItem(player, {
+// selectable: true,
+// label: 'Auto',
+// });
+
+// const items = qualities.map(item => {
+// const newMenuItem = new MenuItem(player, {
+// selectable: true,
+// label: item.name,
+// });
+
+// // Quality selected
+// newMenuItem.on('click', () => {
+// // If for some reason tech doesn't exist, then don't do anything
+// if (!tech) {
+// console.warn('Invalid attempt to access null player tech');
+// return;
+// }
+// // Only enable this single, selected representation.
+// tech.vhs.representations().forEach((rep, index) => {
+// rep.enabled(index === item.index);
+// });
+// newMenuItem.selected(false);
+// });
+
+// return newMenuItem;
+// });
+
+// defaultAutoItem.on('click', () => {
+// // Re-enable all representations.
+// tech.vhs.representations().forEach((rep, index) => {
+// rep.enabled(true);
+// });
+// defaultAutoItem.selected(false);
+// });
+
+// const supportsLatencyCompensator = !!tech && !!tech.vhs;
+
+// // Only show the quality selector if there is more than one option.
+// if (qualities.length < 2 && supportsLatencyCompensator) {
+// return [lowLatencyItem];
+// }
+// if (qualities.length > 1 && supportsLatencyCompensator) {
+// return [defaultAutoItem, ...items, separator, lowLatencyItem];
+// }
+// if (!supportsLatencyCompensator && qualities.length == 1) {
+// return [];
+// }
+
+// return [defaultAutoItem, ...items];
+// },
+// });
+
+// // If none of the settings in this menu are applicable then don't show it.
+// const tech = player.tech({ IWillNotUseThisInPlugins: true });
+
+// if (qualities.length < 2 && (!tech || !tech.vhs)) {
+// return;
+// }
+
+// const menuButton = new MenuButton();
+// menuButton.addClass('vjs-quality-selector');
+// player.controlBar.addChild(menuButton, {}, player.controlBar.children_.length - 2);
+// this.qualityMenuButton = menuButton;
+// this.latencyCompensatorToggleButton = lowLatencyItem;
+// });
+// }
+
+// addAirplay() {
+// videojs.hookOnce('setup', player => {
+// if (window.WebKitPlaybackTargetAvailabilityEvent) {
+// const videoJsButtonClass = videojs.getComponent('Button');
+// const concreteButtonClass = videojs.extend(videoJsButtonClass, {
+// // 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() {
+// videoJsButtonClass.call(this, player);
+// },
+
+// handleClick() {
+// const videoElement = document.getElementsByTagName('video')[0];
+// videoElement.webkitShowPlaybackTargetPicker();
+// },
+// });
+
+// const concreteButtonInstance = player.controlBar.addChild(new concreteButtonClass());
+// concreteButtonInstance.addClass('vjs-airplay');
+// }
+// });
+// }
+
+// }
+
+// export { OwncastPlayer };
+
+// const VjsMenuItem = videojs.getComponent('MenuItem');
+
+// VjsMenuItem.registerComponent('MenuSeparator', MenuSeparator);
diff --git a/web/package-lock.json b/web/package-lock.json
index abb67411d..5751fc59e 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -31,7 +31,8 @@
"react-markdown": "8.0.0",
"react-markdown-editor-lite": "1.3.2",
"recoil": "^0.7.2",
- "ua-parser-js": "1.0.2"
+ "ua-parser-js": "1.0.2",
+ "video.js": "^7.18.1"
},
"devDependencies": {
"@babel/core": "^7.17.9",
@@ -11392,6 +11393,66 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@videojs/http-streaming": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.13.1.tgz",
+ "integrity": "sha512-1x3fkGSPyL0+iaS3/lTvfnPTtfqzfgG+ELQtPPtTvDwqGol9Mx3TNyZwtSTdIufBrqYRn7XybB/3QNMsyjq13A==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "3.0.4",
+ "aes-decrypter": "3.1.2",
+ "global": "^4.4.0",
+ "m3u8-parser": "4.7.0",
+ "mpd-parser": "0.21.0",
+ "mux.js": "6.0.1",
+ "video.js": "^6 || ^7"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ },
+ "peerDependencies": {
+ "video.js": "^6 || ^7"
+ }
+ },
+ "node_modules/@videojs/http-streaming/node_modules/@videojs/vhs-utils": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz",
+ "integrity": "sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "global": "^4.4.0",
+ "url-toolkit": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ }
+ },
+ "node_modules/@videojs/vhs-utils": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
+ "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "global": "^4.4.0",
+ "url-toolkit": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ }
+ },
+ "node_modules/@videojs/xhr": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz",
+ "integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "global": "~4.4.0",
+ "is-function": "^1.0.1"
+ }
+ },
"node_modules/@webassemblyjs/ast": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@@ -11622,6 +11683,14 @@
"@xtuc/long": "4.2.2"
}
},
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz",
+ "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/@xobotyi/scrollbar-width": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz",
@@ -11815,6 +11884,17 @@
"node": ">= 0.12.0"
}
},
+ "node_modules/aes-decrypter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.2.tgz",
+ "integrity": "sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^3.0.0",
+ "global": "^4.4.0",
+ "pkcs7": "^1.0.4"
+ }
+ },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -19119,6 +19199,11 @@
"node": ">=8"
}
},
+ "node_modules/individual": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz",
+ "integrity": "sha1-gzsJfa0jKU52EXqY+zjg2a1hu5c="
+ },
"node_modules/infer-owner": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
@@ -22302,6 +22387,11 @@
"node": ">=8"
}
},
+ "node_modules/keycode": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
+ "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg=="
+ },
"node_modules/keyv": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
@@ -22612,6 +22702,16 @@
"lz-string": "bin/bin.js"
}
},
+ "node_modules/m3u8-parser": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.0.tgz",
+ "integrity": "sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^3.0.0",
+ "global": "^4.4.0"
+ }
+ },
"node_modules/make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -23641,6 +23741,20 @@
"rimraf": "bin.js"
}
},
+ "node_modules/mpd-parser": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.0.tgz",
+ "integrity": "sha512-NbpMJ57qQzFmfCiP1pbL7cGMbVTD0X1hqNgL0VYP1wLlZXLf/HtmvQpNkOA1AHkPVeGQng+7/jEtSvNUzV7Gdg==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^3.0.2",
+ "@xmldom/xmldom": "^0.7.2",
+ "global": "^4.4.0"
+ },
+ "bin": {
+ "mpd-to-m3u8-json": "bin/parse.js"
+ }
+ },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -23654,6 +23768,22 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "node_modules/mux.js": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
+ "integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
+ "dependencies": {
+ "@babel/runtime": "^7.11.2",
+ "global": "^4.4.0"
+ },
+ "bin": {
+ "muxjs-transmux": "bin/transmux.js"
+ },
+ "engines": {
+ "node": ">=8",
+ "npm": ">=5"
+ }
+ },
"node_modules/nan": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
@@ -24805,6 +24935,17 @@
"node": ">= 6"
}
},
+ "node_modules/pkcs7": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz",
+ "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5"
+ },
+ "bin": {
+ "pkcs7": "bin/cli.js"
+ }
+ },
"node_modules/pkg-dir": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
@@ -27598,6 +27739,14 @@
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
+ "node_modules/rust-result": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz",
+ "integrity": "sha1-NMdbLm3Dn+WHXlveyFteD5FTb3I=",
+ "dependencies": {
+ "individual": "^2.0.0"
+ }
+ },
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -27614,6 +27763,14 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
+ "node_modules/safe-json-parse": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz",
+ "integrity": "sha1-fA9XjPzNEtM6ccDgVBPi7KFx6qw=",
+ "dependencies": {
+ "rust-result": "^1.0.0"
+ }
+ },
"node_modules/safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
@@ -30377,6 +30534,11 @@
"node": ">=4"
}
},
+ "node_modules/url-toolkit": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
+ "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="
+ },
"node_modules/url/node_modules/punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
@@ -30580,6 +30742,39 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/video.js": {
+ "version": "7.18.1",
+ "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.18.1.tgz",
+ "integrity": "sha512-mnXdmkVcD5qQdKMZafDjqdhrnKGettZaGSVkExjACiylSB4r2Yt5W1bchsKmjFpfuNfszsMjTUnnoIWSSqoe/Q==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/http-streaming": "2.13.1",
+ "@videojs/vhs-utils": "^3.0.4",
+ "@videojs/xhr": "2.6.0",
+ "aes-decrypter": "3.1.2",
+ "global": "^4.4.0",
+ "keycode": "^2.2.0",
+ "m3u8-parser": "4.7.0",
+ "mpd-parser": "0.21.0",
+ "mux.js": "6.0.1",
+ "safe-json-parse": "4.0.0",
+ "videojs-font": "3.2.0",
+ "videojs-vtt.js": "^0.15.3"
+ }
+ },
+ "node_modules/videojs-font": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz",
+ "integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA=="
+ },
+ "node_modules/videojs-vtt.js": {
+ "version": "0.15.3",
+ "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.3.tgz",
+ "integrity": "sha512-5FvVsICuMRx6Hd7H/Y9s9GDeEtYcXQWzGMS+sl4UX3t/zoHp3y+isSfIPRochnTH7h+Bh1ILyC639xy9Z6kPag==",
+ "dependencies": {
+ "global": "^4.3.1"
+ }
+ },
"node_modules/vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@@ -39893,6 +40088,53 @@
"eslint-visitor-keys": "^3.0.0"
}
},
+ "@videojs/http-streaming": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.13.1.tgz",
+ "integrity": "sha512-1x3fkGSPyL0+iaS3/lTvfnPTtfqzfgG+ELQtPPtTvDwqGol9Mx3TNyZwtSTdIufBrqYRn7XybB/3QNMsyjq13A==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "3.0.4",
+ "aes-decrypter": "3.1.2",
+ "global": "^4.4.0",
+ "m3u8-parser": "4.7.0",
+ "mpd-parser": "0.21.0",
+ "mux.js": "6.0.1",
+ "video.js": "^6 || ^7"
+ },
+ "dependencies": {
+ "@videojs/vhs-utils": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.4.tgz",
+ "integrity": "sha512-hui4zOj2I1kLzDgf8QDVxD3IzrwjS/43KiS8IHQO0OeeSsb4pB/lgNt1NG7Dv0wMQfCccUpMVLGcK618s890Yg==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "global": "^4.4.0",
+ "url-toolkit": "^2.2.1"
+ }
+ }
+ }
+ },
+ "@videojs/vhs-utils": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-3.0.5.tgz",
+ "integrity": "sha512-PKVgdo8/GReqdx512F+ombhS+Bzogiofy1LgAj4tN8PfdBx3HSS7V5WfJotKTqtOWGwVfSWsrYN/t09/DSryrw==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "global": "^4.4.0",
+ "url-toolkit": "^2.2.1"
+ }
+ },
+ "@videojs/xhr": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/@videojs/xhr/-/xhr-2.6.0.tgz",
+ "integrity": "sha512-7J361GiN1tXpm+gd0xz2QWr3xNWBE+rytvo8J3KuggFaLg+U37gZQ2BuPLcnkfGffy2e+ozY70RHC8jt7zjA6Q==",
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "global": "~4.4.0",
+ "is-function": "^1.0.1"
+ }
+ },
"@webassemblyjs/ast": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@@ -40129,6 +40371,11 @@
"@xtuc/long": "4.2.2"
}
},
+ "@xmldom/xmldom": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz",
+ "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A=="
+ },
"@xobotyi/scrollbar-width": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz",
@@ -40282,6 +40529,17 @@
"resolved": "https://registry.npmjs.org/address/-/address-1.1.2.tgz",
"integrity": "sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA=="
},
+ "aes-decrypter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.2.tgz",
+ "integrity": "sha512-42nRwfQuPRj9R1zqZBdoxnaAmnIFyDi0MNyTVhjdFOd8fifXKKRfwIHIZ6AMn1or4x5WONzjwRTbTWcsIQ0O4A==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^3.0.0",
+ "global": "^4.4.0",
+ "pkcs7": "^1.0.4"
+ }
+ },
"agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -45949,6 +46207,11 @@
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
},
+ "individual": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/individual/-/individual-2.0.0.tgz",
+ "integrity": "sha1-gzsJfa0jKU52EXqY+zjg2a1hu5c="
+ },
"infer-owner": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
@@ -48473,6 +48736,11 @@
"resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz",
"integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ=="
},
+ "keycode": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.1.tgz",
+ "integrity": "sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg=="
+ },
"keyv": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
@@ -48713,6 +48981,16 @@
"integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=",
"dev": true
},
+ "m3u8-parser": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-4.7.0.tgz",
+ "integrity": "sha512-48l/OwRyjBm+QhNNigEEcRcgbRvnUjL7rxs597HmW9QSNbyNvt+RcZ9T/d9vxi9A9z7EZrB1POtZYhdRlwYQkQ==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^3.0.0",
+ "global": "^4.4.0"
+ }
+ },
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
@@ -49409,6 +49687,17 @@
}
}
},
+ "mpd-parser": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-0.21.0.tgz",
+ "integrity": "sha512-NbpMJ57qQzFmfCiP1pbL7cGMbVTD0X1hqNgL0VYP1wLlZXLf/HtmvQpNkOA1AHkPVeGQng+7/jEtSvNUzV7Gdg==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/vhs-utils": "^3.0.2",
+ "@xmldom/xmldom": "^0.7.2",
+ "global": "^4.4.0"
+ }
+ },
"mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@@ -49419,6 +49708,15 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "mux.js": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/mux.js/-/mux.js-6.0.1.tgz",
+ "integrity": "sha512-22CHb59rH8pWGcPGW5Og7JngJ9s+z4XuSlYvnxhLuc58cA1WqGDQPzuG8I+sPm1/p0CdgpzVTaKW408k5DNn8w==",
+ "requires": {
+ "@babel/runtime": "^7.11.2",
+ "global": "^4.4.0"
+ }
+ },
"nan": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz",
@@ -50323,6 +50621,14 @@
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
"integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ=="
},
+ "pkcs7": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz",
+ "integrity": "sha512-afRERtHn54AlwaF2/+LFszyAANTCggGilmcmILUzEjvs3XgFZT+xE6+QWQcAGmu4xajy+Xtj7acLOPdx5/eXWQ==",
+ "requires": {
+ "@babel/runtime": "^7.5.5"
+ }
+ },
"pkg-dir": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
@@ -52383,6 +52689,14 @@
}
}
},
+ "rust-result": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/rust-result/-/rust-result-1.0.0.tgz",
+ "integrity": "sha1-NMdbLm3Dn+WHXlveyFteD5FTb3I=",
+ "requires": {
+ "individual": "^2.0.0"
+ }
+ },
"sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -52396,6 +52710,14 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
+ "safe-json-parse": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-4.0.0.tgz",
+ "integrity": "sha1-fA9XjPzNEtM6ccDgVBPi7KFx6qw=",
+ "requires": {
+ "rust-result": "^1.0.0"
+ }
+ },
"safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
@@ -54539,6 +54861,11 @@
"prepend-http": "^2.0.0"
}
},
+ "url-toolkit": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
+ "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg=="
+ },
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
@@ -54680,6 +55007,39 @@
"unist-util-stringify-position": "^3.0.0"
}
},
+ "video.js": {
+ "version": "7.18.1",
+ "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.18.1.tgz",
+ "integrity": "sha512-mnXdmkVcD5qQdKMZafDjqdhrnKGettZaGSVkExjACiylSB4r2Yt5W1bchsKmjFpfuNfszsMjTUnnoIWSSqoe/Q==",
+ "requires": {
+ "@babel/runtime": "^7.12.5",
+ "@videojs/http-streaming": "2.13.1",
+ "@videojs/vhs-utils": "^3.0.4",
+ "@videojs/xhr": "2.6.0",
+ "aes-decrypter": "3.1.2",
+ "global": "^4.4.0",
+ "keycode": "^2.2.0",
+ "m3u8-parser": "4.7.0",
+ "mpd-parser": "0.21.0",
+ "mux.js": "6.0.1",
+ "safe-json-parse": "4.0.0",
+ "videojs-font": "3.2.0",
+ "videojs-vtt.js": "^0.15.3"
+ }
+ },
+ "videojs-font": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/videojs-font/-/videojs-font-3.2.0.tgz",
+ "integrity": "sha512-g8vHMKK2/JGorSfqAZQUmYYNnXmfec4MLhwtEFS+mMs2IDY398GLysy6BH6K+aS1KMNu/xWZ8Sue/X/mdQPliA=="
+ },
+ "videojs-vtt.js": {
+ "version": "0.15.3",
+ "resolved": "https://registry.npmjs.org/videojs-vtt.js/-/videojs-vtt.js-0.15.3.tgz",
+ "integrity": "sha512-5FvVsICuMRx6Hd7H/Y9s9GDeEtYcXQWzGMS+sl4UX3t/zoHp3y+isSfIPRochnTH7h+Bh1ILyC639xy9Z6kPag==",
+ "requires": {
+ "global": "^4.3.1"
+ }
+ },
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
diff --git a/web/package.json b/web/package.json
index 0a14eadae..d52a4f01e 100644
--- a/web/package.json
+++ b/web/package.json
@@ -34,7 +34,8 @@
"react-markdown": "8.0.0",
"react-markdown-editor-lite": "1.3.2",
"recoil": "^0.7.2",
- "ua-parser-js": "1.0.2"
+ "ua-parser-js": "1.0.2",
+ "video.js": "^7.18.1"
},
"devDependencies": {
"@babel/core": "^7.17.9",
diff --git a/web/stories/Dropdown.stories.tsx b/web/stories/Dropdown.stories.tsx
index 46cf854e5..dd0faa70e 100644
--- a/web/stories/Dropdown.stories.tsx
+++ b/web/stories/Dropdown.stories.tsx
@@ -25,7 +25,7 @@ const DropdownExample = () => (
);
export default {
- title: 'owncast/Dropdown',
+ title: 'example/Dropdown',
component: Dropdown,
parameters: {},
} as ComponentMeta;
diff --git a/web/stories/Introduction.stories.mdx b/web/stories/Introduction.stories.mdx
index 869019fea..ed8f82f80 100644
--- a/web/stories/Introduction.stories.mdx
+++ b/web/stories/Introduction.stories.mdx
@@ -8,7 +8,7 @@ import Plugin from './assets/plugin.svg';
import Repo from './assets/repo.svg';
import StackAlt from './assets/stackalt.svg';
-
+