From 0c439ccba45671025ecced32201212db727ea155 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Tue, 21 Jun 2022 15:38:37 -0700 Subject: [PATCH] Add AirPlay support to player. Closes #1963 --- web/components/video/OwncastPlayer.tsx | 98 +++++++++++------- .../img => web/components/video}/airplay.png | Bin web/components/video/player.scss | 6 +- 3 files changed, 66 insertions(+), 38 deletions(-) rename {webroot/img => web/components/video}/airplay.png (100%) diff --git a/web/components/video/OwncastPlayer.tsx b/web/components/video/OwncastPlayer.tsx index 83218ae14..438c7b50c 100644 --- a/web/components/video/OwncastPlayer.tsx +++ b/web/components/video/OwncastPlayer.tsx @@ -122,6 +122,67 @@ export default function OwncastPlayer(props: Props) { } }; + const setupLatencyCompensator = player => { + const tech = player.tech({ IWillNotUseThisInPlugins: true }); + + // VHS is required. + if (!tech || !tech.vhs) { + return; + } + + const latencyCompensatorEnabledSaved = getLocalStorage(LATENCY_COMPENSATION_ENABLED); + + if (latencyCompensatorEnabledSaved === 'true' && tech && tech.vhs) { + startLatencyCompensator(); + } else { + stopLatencyCompensator(); + } + }; + + const createSettings = async (player, videojs) => { + const videoQualities = await getVideoSettings(); + const menuButton = createVideoSettingsMenuButton( + player, + videojs, + videoQualities, + toggleLatencyCompensator, + ); + player.controlBar.addChild( + menuButton, + {}, + // eslint-disable-next-line no-underscore-dangle + player.controlBar.children_.length - 2, + ); + setupLatencyCompensator(player); + }; + + const setupAirplay = (player, videojs) => { + // eslint-disable-next-line no-prototype-builtins + if (window.hasOwnProperty('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() { + try { + const videoElement = document.getElementsByTagName('video')[0]; + (videoElement as any).webkitShowPlaybackTargetPicker(); + } catch (e) { + console.error(e); + } + }, + }); + + const concreteButtonInstance = player.controlBar.addChild(new ConcreteButtonClass()); + concreteButtonInstance.addClass('vjs-airplay'); + } + }; + // Register keyboard shortcut for the space bar to toggle playback useHotkeys('space', togglePlayback, { enableOnContentEditable: false, @@ -181,23 +242,7 @@ export default function OwncastPlayer(props: Props) { const handlePlayerReady = (player, videojs) => { playerRef.current = player; setSavedVolume(); - - const setupLatencyCompensator = () => { - const tech = player.tech({ IWillNotUseThisInPlugins: true }); - - // VHS is required. - if (!tech || !tech.vhs) { - return; - } - - const latencyCompensatorEnabledSaved = getLocalStorage(LATENCY_COMPENSATION_ENABLED); - - if (latencyCompensatorEnabledSaved === 'true' && tech && tech.vhs) { - startLatencyCompensator(); - } else { - stopLatencyCompensator(); - } - }; + setupAirplay(player, videojs); // You can handle player events here, for example: player.on('waiting', () => { @@ -234,24 +279,7 @@ export default function OwncastPlayer(props: Props) { playbackMetrics = new PlaybackMetrics(player, videojs); playbackMetrics.setClockSkew(clockSkew); - const createSettings = async () => { - const videoQualities = await getVideoSettings(); - const menuButton = createVideoSettingsMenuButton( - player, - videojs, - videoQualities, - toggleLatencyCompensator, - ); - player.controlBar.addChild( - menuButton, - {}, - // eslint-disable-next-line no-underscore-dangle - player.controlBar.children_.length - 2, - ); - setupLatencyCompensator(); - }; - - createSettings(); + createSettings(player, videojs); }; useEffect(() => { diff --git a/webroot/img/airplay.png b/web/components/video/airplay.png similarity index 100% rename from webroot/img/airplay.png rename to web/components/video/airplay.png diff --git a/web/components/video/player.scss b/web/components/video/player.scss index 2796ea4c9..a4c58be5a 100644 --- a/web/components/video/player.scss +++ b/web/components/video/player.scss @@ -13,9 +13,9 @@ background-color: var(--theme-background) !important; } -// .vjs-airplay .vjs-icon-placeholder::before { -// content: url("../img/airplay.png"); -// } +.vjs-airplay .vjs-icon-placeholder::before { + content: url("./airplay.png"); +} .vjs-quality-selector .vjs-icon-placeholder { font-family: VideoJS;