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'; - +