diff --git a/src/components/player/atoms/Airplay.tsx b/src/components/player/atoms/Airplay.tsx new file mode 100644 index 00000000..0d517fc8 --- /dev/null +++ b/src/components/player/atoms/Airplay.tsx @@ -0,0 +1,17 @@ +import { Icons } from "@/components/Icon"; +import { VideoPlayerButton } from "@/components/player/internals/Button"; +import { usePlayerStore } from "@/stores/player/store"; + +export function Airplay() { + const canAirplay = usePlayerStore((s) => s.interface.canAirplay); + const display = usePlayerStore((s) => s.display); + + if (!canAirplay) return null; + + return ( + display?.startAirplay()} + icon={Icons.AIRPLAY} + /> + ); +} diff --git a/src/components/player/atoms/index.ts b/src/components/player/atoms/index.ts index e72ef50f..2c71620c 100644 --- a/src/components/player/atoms/index.ts +++ b/src/components/player/atoms/index.ts @@ -10,3 +10,4 @@ export * from "./Title"; export * from "./EpisodeTitle"; export * from "./Settings"; export * from "./Episodes"; +export * from "./Airplay"; diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts index 9a806daa..3eed12ee 100644 --- a/src/components/player/display/base.ts +++ b/src/components/player/display/base.ts @@ -85,6 +85,14 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { emit("fullscreen", isFullscreen); if (!isFullscreen) emit("needstrack", false); }); + videoElement.addEventListener( + "webkitplaybacktargetavailabilitychanged", + (e: any) => { + if (e.availability === "available") { + emit("canairplay", true); + } + } + ); } function unloadSource() { @@ -206,5 +214,11 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { } } }, + startAirplay() { + const videoPlayer = videoElement as any; + if (videoPlayer && videoPlayer.webkitShowPlaybackTargetPicker) { + videoPlayer.webkitShowPlaybackTargetPicker(); + } + }, }; } diff --git a/src/components/player/display/displayInterface.ts b/src/components/player/display/displayInterface.ts index ed8c10de..a54207b6 100644 --- a/src/components/player/display/displayInterface.ts +++ b/src/components/player/display/displayInterface.ts @@ -13,6 +13,7 @@ export type DisplayInterfaceEvents = { qualities: SourceQuality[]; changedquality: SourceQuality | null; needstrack: boolean; + canairplay: boolean; }; export interface DisplayInterface extends Listener { @@ -26,4 +27,5 @@ export interface DisplayInterface extends Listener { setVolume(vol: number): void; setTime(t: number): void; destroy(): void; + startAirplay(): void; } diff --git a/src/pages/parts/player/PlayerPart.tsx b/src/pages/parts/player/PlayerPart.tsx index 795d7dc6..3d6973f4 100644 --- a/src/pages/parts/player/PlayerPart.tsx +++ b/src/pages/parts/player/PlayerPart.tsx @@ -72,6 +72,7 @@ export function PlayerPart(props: PlayerPartProps) {
+
diff --git a/src/stores/player/slices/display.ts b/src/stores/player/slices/display.ts index 76597086..1fbe7efc 100644 --- a/src/stores/player/slices/display.ts +++ b/src/stores/player/slices/display.ts @@ -80,6 +80,11 @@ export const createDisplaySlice: MakeSlice = (set, get) => ({ s.caption.asTrack = needsTrack; }); }); + newDisplay.on("canairplay", (canAirplay) => { + set((s) => { + s.interface.canAirplay = canAirplay; + }); + }); set((s) => { s.display = newDisplay; diff --git a/src/stores/player/slices/interface.ts b/src/stores/player/slices/interface.ts index a08b2313..d5faa461 100644 --- a/src/stores/player/slices/interface.ts +++ b/src/stores/player/slices/interface.ts @@ -19,6 +19,7 @@ export interface InterfaceSlice { hasOpenOverlay: boolean; hovering: PlayerHoverState; lastHoveringState: PlayerHoverState; + canAirplay: boolean; volumeChangedWithKeybind: boolean; // has the volume recently been adjusted with the up/down arrows recently? volumeChangedWithKeybindDebounce: NodeJS.Timeout | null; // debounce for the duration of the "volume changed thingamajig" @@ -46,6 +47,7 @@ export const createInterfaceSlice: MakeSlice = (set, get) => ({ volumeChangedWithKeybind: false, volumeChangedWithKeybindDebounce: null, timeFormat: VideoPlayerTimeFormat.REGULAR, + canAirplay: false, }, setLastVolume(state) {