mirror of https://github.com/stashapp/stash.git
Add AirPlay and Chromecast support (#2872)
* dynamically load cast_sender.js * add https://www.gstatic.com to connectableOrigins * Add toggle for chromecast
This commit is contained in:
parent
c499c20a7b
commit
8e235a26ee
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Build Frontend
|
||||
FROM node:alpine as frontend
|
||||
RUN apk add --no-cache make
|
||||
RUN apk add --no-cache make git
|
||||
## cache node_modules separately
|
||||
COPY ./ui/v2.5/package.json ./ui/v2.5/yarn.lock /stash/ui/v2.5/
|
||||
WORKDIR /stash
|
||||
|
|
|
@ -490,7 +490,7 @@ func setPageSecurityHeaders(w http.ResponseWriter, r *http.Request) {
|
|||
// The graphql playground pulls its frontend from a cdn
|
||||
if r.URL.Path == playgroundEndpoint {
|
||||
connectSrc += " https://cdn.jsdelivr.net"
|
||||
scriptSrc += " https://cdn.jsdelivr.net"
|
||||
scriptSrc += " https://cdn.jsdelivr.net http://www.gstatic.com https://www.gstatic.com"
|
||||
styleSrc += " https://cdn.jsdelivr.net"
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@
|
|||
"@fortawesome/free-regular-svg-icons": "^6.3.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.3.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@silvermine/videojs-airplay": "^1.2.0",
|
||||
"@silvermine/videojs-chromecast": "^1.4.1",
|
||||
"apollo-upload-client": "^17.0.0",
|
||||
"axios": "^1.3.3",
|
||||
"base64-blob": "^1.4.1",
|
||||
|
|
|
@ -8,6 +8,7 @@ import React, {
|
|||
useState,
|
||||
} from "react";
|
||||
import videojs, { VideoJsPlayer, VideoJsPlayerOptions } from "video.js";
|
||||
import useScript from "src/hooks/useScript";
|
||||
import "videojs-contrib-dash";
|
||||
import "videojs-mobile-ui";
|
||||
import "videojs-seek-buttons";
|
||||
|
@ -22,6 +23,12 @@ import "./big-buttons";
|
|||
import "./track-activity";
|
||||
import "./vrmode";
|
||||
import cx from "classnames";
|
||||
// @ts-ignore
|
||||
import airplay from "@silvermine/videojs-airplay";
|
||||
// @ts-ignore
|
||||
import chromecast from "@silvermine/videojs-chromecast";
|
||||
import "@silvermine/videojs-chromecast/dist/silvermine-videojs-chromecast.css";
|
||||
import "@silvermine/videojs-airplay/dist/silvermine-videojs-airplay.css";
|
||||
import {
|
||||
useSceneSaveActivity,
|
||||
useSceneIncrementPlayCount,
|
||||
|
@ -211,11 +218,15 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
|||
const started = useRef(false);
|
||||
const auto = useRef(false);
|
||||
const interactiveReady = useRef(false);
|
||||
|
||||
const minimumPlayPercent = uiConfig?.minimumPlayPercent ?? 0;
|
||||
const trackActivity = uiConfig?.trackActivity ?? false;
|
||||
const vrTag = uiConfig?.vrTag ?? undefined;
|
||||
|
||||
useScript(
|
||||
"https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1",
|
||||
uiConfig?.enableChromecast
|
||||
);
|
||||
|
||||
const file = useMemo(
|
||||
() => (scene.files.length > 0 ? scene.files[0] : undefined),
|
||||
[scene]
|
||||
|
@ -267,6 +278,8 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
|||
|
||||
// Initialize VideoJS player
|
||||
useEffect(() => {
|
||||
airplay(videojs);
|
||||
chromecast(videojs);
|
||||
const options: VideoJsPlayerOptions = {
|
||||
id: VIDEO_PLAYER_ID,
|
||||
controls: true,
|
||||
|
@ -300,12 +313,15 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
|
|||
inactivityTimeout: 2000,
|
||||
preload: "none",
|
||||
playsinline: true,
|
||||
techOrder: ["chromecast", "html5"],
|
||||
userActions: {
|
||||
hotkeys: function (this: VideoJsPlayer, event) {
|
||||
handleHotkeys(this, event);
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
airPlay: {},
|
||||
chromecast: {},
|
||||
vttThumbnails: {
|
||||
showTimestamp: true,
|
||||
},
|
||||
|
|
|
@ -62,6 +62,12 @@ $sceneTabWidth: 450px;
|
|||
}
|
||||
}
|
||||
|
||||
.vjs-airplay-button .vjs-icon-placeholder,
|
||||
.vjs-chromecast-button .vjs-icon-placeholder {
|
||||
height: 1.6em;
|
||||
width: 1.6em;
|
||||
}
|
||||
|
||||
.vjs-touch-overlay .vjs-play-control {
|
||||
z-index: 1;
|
||||
}
|
||||
|
@ -308,6 +314,12 @@ $sceneTabWidth: 450px;
|
|||
font-size: 1.5em;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.vjs-airplay-button .vjs-icon-placeholder,
|
||||
.vjs-chromecast-button .vjs-icon-placeholder {
|
||||
height: 1.4em;
|
||||
width: 1.4em;
|
||||
}
|
||||
}
|
||||
|
||||
.vjs-menu-button-popup .vjs-menu {
|
||||
|
|
|
@ -272,6 +272,12 @@ export const SettingsInterfacePanel: React.FC = () => {
|
|||
</SettingSection>
|
||||
|
||||
<SettingSection headingID="config.ui.scene_player.heading">
|
||||
<BooleanSetting
|
||||
id="enable-chromecast"
|
||||
headingID="config.ui.scene_player.options.enable_chromecast"
|
||||
checked={ui.enableChromecast ?? undefined}
|
||||
onChange={(v) => saveUI({ enableChromecast: v })}
|
||||
/>
|
||||
<BooleanSetting
|
||||
id="show-scrubber"
|
||||
headingID="config.ui.scene_player.options.show_scrubber"
|
||||
|
|
|
@ -43,6 +43,8 @@ export interface IUIConfig {
|
|||
|
||||
ratingSystemOptions?: RatingSystemOptions;
|
||||
|
||||
// if true the chromecast option will enabled
|
||||
enableChromecast?: boolean;
|
||||
// if true continue scene will always play from the beginning
|
||||
alwaysStartFromBeginning?: boolean;
|
||||
// if true enable activity tracking
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
const useScript = (url: string, condition?: boolean) => {
|
||||
useEffect(() => {
|
||||
const script = document.createElement("script");
|
||||
|
||||
script.src = url;
|
||||
script.async = true;
|
||||
|
||||
if (condition) {
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (condition) {
|
||||
document.head.removeChild(script);
|
||||
}
|
||||
};
|
||||
}, [url, condition]);
|
||||
};
|
||||
|
||||
export default useScript;
|
|
@ -658,6 +658,7 @@
|
|||
"description": "Play next scene in queue when video finishes",
|
||||
"heading": "Continue playlist by default"
|
||||
},
|
||||
"enable_chromecast": "Enable Chromecast",
|
||||
"show_scrubber": "Show Scrubber",
|
||||
"track_activity": "Track Activity",
|
||||
"vr_tag": {
|
||||
|
|
|
@ -2161,6 +2161,18 @@
|
|||
dependencies:
|
||||
dequal "^2.0.2"
|
||||
|
||||
"@silvermine/videojs-airplay@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@silvermine/videojs-airplay/-/videojs-airplay-1.2.0.tgz#3ca6464c8e94c97bb9f35c2926f59a5aa662cebd"
|
||||
integrity sha512-4KzHoM/wJaq3au8IBLxnz8+btAB/M/2AdMoAoOdZRpp9DGG8SGU/UPw2w+CRMknvfbGtvlhlNrNiiXVWA6Cn0A==
|
||||
|
||||
"@silvermine/videojs-chromecast@^1.4.1":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@silvermine/videojs-chromecast/-/videojs-chromecast-1.4.1.tgz#a5763eb85dfd4bc8f47a1b8b150d5dadbb8ea658"
|
||||
integrity sha512-tXeikkWoNC3WIl2WSkIag1CLMbGsgn+26LM4LoB4qx0TQ8mkg7pgpldCTkvXxLVaDluQ/uEm2uxrgrMmjOc6sw==
|
||||
dependencies:
|
||||
webcomponents.js "git+https://git@github.com/webcomponents/webcomponentsjs.git#v0.7.24"
|
||||
|
||||
"@tootallnate/once@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
|
@ -8143,6 +8155,10 @@ web-streams-polyfill@^3.2.1:
|
|||
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
|
||||
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
|
||||
|
||||
"webcomponents.js@git+https://git@github.com/webcomponents/webcomponentsjs.git#v0.7.24":
|
||||
version "0.7.24"
|
||||
resolved "git+https://git@github.com/webcomponents/webcomponentsjs.git#8a2e40557b177e2cca0def2553f84c8269c8f93e"
|
||||
|
||||
webcrypto-core@^1.7.4:
|
||||
version "1.7.6"
|
||||
resolved "https://registry.yarnpkg.com/webcrypto-core/-/webcrypto-core-1.7.6.tgz#e32c4a12a13de4251f8f9ef336a6cba7cdec9b55"
|
||||
|
|
Loading…
Reference in New Issue