mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-10 22:35:09 +01:00
bottom control layout + fullscreen + hovering
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
a813efe5ba
commit
7e182a4b7a
@ -1,2 +1,3 @@
|
||||
export * from "./atoms";
|
||||
export * from "./base/Container";
|
||||
export * from "./base/BottomControls";
|
||||
|
15
src/components/player/atoms/Fullscreen.tsx
Normal file
15
src/components/player/atoms/Fullscreen.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
export function Fullscreen() {
|
||||
const { isFullscreen } = usePlayerStore((s) => s.interface);
|
||||
const display = usePlayerStore((s) => s.display);
|
||||
|
||||
return (
|
||||
<VideoPlayerButton
|
||||
onClick={() => display?.toggleFullscreen()}
|
||||
icon={isFullscreen ? Icons.COMPRESS : Icons.EXPAND}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
export function Pause() {
|
||||
@ -10,8 +12,9 @@ export function Pause() {
|
||||
};
|
||||
|
||||
return (
|
||||
<button type="button" onClick={toggle}>
|
||||
play/pause
|
||||
</button>
|
||||
<VideoPlayerButton
|
||||
onClick={toggle}
|
||||
icon={isPaused ? Icons.PLAY : Icons.PAUSE}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1 +1,2 @@
|
||||
export * from "./pause";
|
||||
export * from "./Pause";
|
||||
export * from "./Fullscreen";
|
||||
|
18
src/components/player/base/BottomControls.tsx
Normal file
18
src/components/player/base/BottomControls.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Transition } from "@/components/Transition";
|
||||
|
||||
export function BottomControls(props: {
|
||||
show: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="w-full absolute bottom-0 flex flex-col pt-32 bg-gradient-to-t from-black to-transparent [margin-bottom:env(safe-area-inset-bottom)]">
|
||||
<Transition
|
||||
animation="slide-up"
|
||||
show={props.show}
|
||||
className="pointer-events-auto px-4 pb-2 flex justify-end"
|
||||
>
|
||||
{props.children}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,16 +1,90 @@
|
||||
import { ReactNode } from "react";
|
||||
import { ReactNode, RefObject, useEffect, useRef } from "react";
|
||||
|
||||
import { VideoContainer } from "@/components/player/internals/VideoContainer";
|
||||
import { PlayerHoverState } from "@/stores/player/slices/interface";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
export interface PlayerProps {
|
||||
children?: ReactNode;
|
||||
onLoad?: () => void;
|
||||
}
|
||||
|
||||
export function Container(props: PlayerProps) {
|
||||
function useHovering(containerEl: RefObject<HTMLDivElement>) {
|
||||
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const updateInterfaceHovering = usePlayerStore(
|
||||
(s) => s.updateInterfaceHovering
|
||||
);
|
||||
const hovering = usePlayerStore((s) => s.interface.hovering);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerEl.current) return;
|
||||
const el = containerEl.current;
|
||||
|
||||
function pointerMove(e: PointerEvent) {
|
||||
if (e.pointerType !== "mouse") return;
|
||||
updateInterfaceHovering(PlayerHoverState.MOUSE_HOVER);
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
|
||||
timeoutRef.current = null;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function pointerLeave(e: PointerEvent) {
|
||||
if (e.pointerType !== "mouse") return;
|
||||
updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
function pointerUp(e: PointerEvent) {
|
||||
if (e.pointerType === "mouse") return;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
if (hovering !== PlayerHoverState.MOBILE_TAPPED)
|
||||
updateInterfaceHovering(PlayerHoverState.MOBILE_TAPPED);
|
||||
else updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
|
||||
}
|
||||
|
||||
el.addEventListener("pointermove", pointerMove);
|
||||
el.addEventListener("pointerleave", pointerLeave);
|
||||
el.addEventListener("pointerup", pointerUp);
|
||||
|
||||
return () => {
|
||||
el.removeEventListener("pointermove", pointerMove);
|
||||
el.removeEventListener("pointerleave", pointerLeave);
|
||||
el.removeEventListener("pointerup", pointerUp);
|
||||
};
|
||||
}, [containerEl, hovering, updateInterfaceHovering]);
|
||||
}
|
||||
|
||||
function BaseContainer(props: { children?: ReactNode }) {
|
||||
const containerEl = useRef<HTMLDivElement | null>(null);
|
||||
const display = usePlayerStore((s) => s.display);
|
||||
useHovering(containerEl);
|
||||
|
||||
// report container element to display interface
|
||||
useEffect(() => {
|
||||
if (display && containerEl.current) {
|
||||
display.processContainerElement(containerEl.current);
|
||||
}
|
||||
}, [display, containerEl]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<VideoContainer />
|
||||
<div className="relative overflow-hidden h-screen" ref={containerEl}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Container(props: PlayerProps) {
|
||||
const propRef = useRef(props.onLoad);
|
||||
useEffect(() => {
|
||||
propRef.current?.();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<BaseContainer>
|
||||
<VideoContainer />
|
||||
{props.children}
|
||||
</BaseContainer>
|
||||
);
|
||||
}
|
||||
|
@ -1,14 +1,23 @@
|
||||
import fscreen from "fscreen";
|
||||
|
||||
import {
|
||||
DisplayInterface,
|
||||
DisplayInterfaceEvents,
|
||||
} from "@/components/player/display/displayInterface";
|
||||
import { Source } from "@/components/player/hooks/usePlayer";
|
||||
import {
|
||||
canFullscreen,
|
||||
canFullscreenAnyElement,
|
||||
canWebkitFullscreen,
|
||||
} from "@/utils/detectFeatures";
|
||||
import { makeEmitter } from "@/utils/events";
|
||||
|
||||
export function makeVideoElementDisplayInterface(): DisplayInterface {
|
||||
const { emit, on, off } = makeEmitter<DisplayInterfaceEvents>();
|
||||
let source: Source | null = null;
|
||||
let videoElement: HTMLVideoElement | null = null;
|
||||
let containerElement: HTMLElement | null = null;
|
||||
let isFullscreen = false;
|
||||
|
||||
function setSource() {
|
||||
if (!videoElement || !source) return;
|
||||
@ -17,13 +26,19 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
||||
videoElement.addEventListener("pause", () => emit("pause", undefined));
|
||||
}
|
||||
|
||||
function fullscreenChange() {
|
||||
isFullscreen =
|
||||
!!document.fullscreenElement || // other browsers
|
||||
!!(document as any).webkitFullscreenElement; // safari
|
||||
}
|
||||
fscreen.addEventListener("fullscreenchange", fullscreenChange);
|
||||
|
||||
return {
|
||||
on,
|
||||
off,
|
||||
|
||||
// no need to destroy anything
|
||||
destroy: () => {},
|
||||
|
||||
destroy: () => {
|
||||
fscreen.removeEventListener("fullscreenchange", fullscreenChange);
|
||||
},
|
||||
load(newSource) {
|
||||
source = newSource;
|
||||
setSource();
|
||||
@ -33,13 +48,36 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
||||
videoElement = video;
|
||||
setSource();
|
||||
},
|
||||
processContainerElement(container) {
|
||||
containerElement = container;
|
||||
},
|
||||
|
||||
pause() {
|
||||
videoElement?.pause();
|
||||
},
|
||||
|
||||
play() {
|
||||
videoElement?.play();
|
||||
},
|
||||
toggleFullscreen() {
|
||||
if (isFullscreen) {
|
||||
isFullscreen = false;
|
||||
emit("fullscreen", isFullscreen);
|
||||
if (!fscreen.fullscreenElement) return;
|
||||
fscreen.exitFullscreen();
|
||||
return;
|
||||
}
|
||||
|
||||
// enter fullscreen
|
||||
isFullscreen = true;
|
||||
emit("fullscreen", isFullscreen);
|
||||
if (!canFullscreen() || fscreen.fullscreenElement) return;
|
||||
if (canFullscreenAnyElement()) {
|
||||
if (containerElement) fscreen.requestFullscreen(containerElement);
|
||||
return;
|
||||
}
|
||||
if (canWebkitFullscreen()) {
|
||||
if (videoElement) (videoElement as any).webkitEnterFullscreen();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { Listener } from "@/utils/events";
|
||||
export type DisplayInterfaceEvents = {
|
||||
play: void;
|
||||
pause: void;
|
||||
fullscreen: boolean;
|
||||
};
|
||||
|
||||
export interface DisplayInterface extends Listener<DisplayInterfaceEvents> {
|
||||
@ -11,5 +12,7 @@ export interface DisplayInterface extends Listener<DisplayInterfaceEvents> {
|
||||
pause(): void;
|
||||
load(source: Source): void;
|
||||
processVideoElement(video: HTMLVideoElement): void;
|
||||
processContainerElement(container: HTMLElement): void;
|
||||
toggleFullscreen(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
|
@ -18,5 +18,8 @@ export function usePlayer() {
|
||||
display?.load(source);
|
||||
setStatus(playerStatus.PLAYING);
|
||||
},
|
||||
setScrapeStatus() {
|
||||
setStatus(playerStatus.SCRAPING);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
18
src/components/player/internals/Button.tsx
Normal file
18
src/components/player/internals/Button.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
|
||||
export function VideoPlayerButton(props: {
|
||||
children?: React.ReactNode;
|
||||
onClick: () => void;
|
||||
icon?: Icons;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onClick}
|
||||
className="p-2 rounded-full hover:bg-video-buttonBackground hover:bg-opacity-75 transition-transform duration-100 active:scale-110 active:bg-opacity-100 active:text-white"
|
||||
>
|
||||
{props.icon && <Icon className="text-2xl" icon={props.icon} />}
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
@ -34,7 +34,7 @@ function VideoElement() {
|
||||
}
|
||||
}, [display, videoEl]);
|
||||
|
||||
return <video autoPlay ref={videoEl} />;
|
||||
return <video className="w-full h-screen" autoPlay ref={videoEl} />;
|
||||
}
|
||||
|
||||
export function VideoContainer() {
|
||||
|
@ -1,24 +1,33 @@
|
||||
import { MWStreamType } from "@/backend/helpers/streams";
|
||||
import { Player } from "@/components/player";
|
||||
import { usePlayer } from "@/components/player/hooks/usePlayer";
|
||||
import { PlayerHoverState } from "@/stores/player/slices/interface";
|
||||
import { playerStatus } from "@/stores/player/slices/source";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
export function PlayerView() {
|
||||
const { status, playMedia } = usePlayer();
|
||||
const { status, playMedia, setScrapeStatus } = usePlayer();
|
||||
const hovering = usePlayerStore((s) => s.interface.hovering);
|
||||
|
||||
function scrape() {
|
||||
playMedia({
|
||||
type: MWStreamType.MP4,
|
||||
url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
||||
// url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
||||
url: "http://95.111.247.180/darude.mp4",
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Player.Container>
|
||||
<Player.Pause />
|
||||
const showControlElements = hovering !== PlayerHoverState.NOT_HOVERING;
|
||||
|
||||
{status === playerStatus.IDLE ? (
|
||||
<div>
|
||||
return (
|
||||
<Player.Container onLoad={setScrapeStatus}>
|
||||
<Player.BottomControls show={showControlElements}>
|
||||
<Player.Pause />
|
||||
<Player.Fullscreen />
|
||||
</Player.BottomControls>
|
||||
|
||||
{status === playerStatus.SCRAPING ? (
|
||||
<div className="w-full h-screen">
|
||||
<p>Its now scraping</p>
|
||||
<button type="button" onClick={scrape}>
|
||||
Finish scraping
|
||||
|
38
src/stores/player/slices/display.ts
Normal file
38
src/stores/player/slices/display.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { DisplayInterface } from "@/components/player/display/displayInterface";
|
||||
import { MakeSlice } from "@/stores/player/slices/types";
|
||||
|
||||
export interface DisplaySlice {
|
||||
display: DisplayInterface | null;
|
||||
setDisplay(display: DisplayInterface): void;
|
||||
}
|
||||
|
||||
export const createDisplaySlice: MakeSlice<DisplaySlice> = (set, get) => ({
|
||||
display: null,
|
||||
setDisplay(newDisplay: DisplayInterface) {
|
||||
const display = get().display;
|
||||
if (display) display.destroy();
|
||||
|
||||
// make display events update the state
|
||||
newDisplay.on("pause", () =>
|
||||
set((s) => {
|
||||
s.mediaPlaying.isPaused = true;
|
||||
s.mediaPlaying.isPlaying = false;
|
||||
})
|
||||
);
|
||||
newDisplay.on("play", () =>
|
||||
set((s) => {
|
||||
s.mediaPlaying.isPaused = false;
|
||||
s.mediaPlaying.isPlaying = true;
|
||||
})
|
||||
);
|
||||
newDisplay.on("fullscreen", (isFullscreen) =>
|
||||
set((s) => {
|
||||
s.interface.isFullscreen = isFullscreen;
|
||||
})
|
||||
);
|
||||
|
||||
set((s) => {
|
||||
s.display = newDisplay;
|
||||
});
|
||||
},
|
||||
});
|
@ -5,9 +5,16 @@ export enum VideoPlayerTimeFormat {
|
||||
REMAINING = 1,
|
||||
}
|
||||
|
||||
export enum PlayerHoverState {
|
||||
NOT_HOVERING = "not_hovering",
|
||||
MOUSE_HOVER = "mouse_hover",
|
||||
MOBILE_TAPPED = "mobile_tapped",
|
||||
}
|
||||
|
||||
export interface InterfaceSlice {
|
||||
interface: {
|
||||
isFullscreen: boolean;
|
||||
hovering: PlayerHoverState;
|
||||
|
||||
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"
|
||||
@ -15,14 +22,23 @@ export interface InterfaceSlice {
|
||||
leftControlHovering: boolean; // is the cursor hovered over the left side of player controls
|
||||
timeFormat: VideoPlayerTimeFormat; // Time format of the video player
|
||||
};
|
||||
updateInterfaceHovering(newState: PlayerHoverState): void;
|
||||
}
|
||||
|
||||
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = () => ({
|
||||
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set) => ({
|
||||
interface: {
|
||||
isFullscreen: false,
|
||||
leftControlHovering: false,
|
||||
hovering: PlayerHoverState.NOT_HOVERING,
|
||||
volumeChangedWithKeybind: false,
|
||||
volumeChangedWithKeybindDebounce: null,
|
||||
timeFormat: VideoPlayerTimeFormat.REGULAR,
|
||||
},
|
||||
|
||||
updateInterfaceHovering(newState: PlayerHoverState) {
|
||||
set((s) => {
|
||||
console.log("setting", newState);
|
||||
s.interface.hovering = newState;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { MWStreamType } from "@/backend/helpers/streams";
|
||||
import { DisplayInterface } from "@/components/player/display/displayInterface";
|
||||
import { MakeSlice } from "@/stores/player/slices/types";
|
||||
import { ValuesOf } from "@/utils/typeguard";
|
||||
|
||||
@ -19,16 +18,13 @@ export interface SourceSliceSource {
|
||||
export interface SourceSlice {
|
||||
status: PlayerStatus;
|
||||
source: SourceSliceSource | null;
|
||||
display: DisplayInterface | null;
|
||||
setStatus(status: PlayerStatus): void;
|
||||
setSource(url: string, type: MWStreamType): void;
|
||||
setDisplay(display: DisplayInterface): void;
|
||||
}
|
||||
|
||||
export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
||||
export const createSourceSlice: MakeSlice<SourceSlice> = (set) => ({
|
||||
source: null,
|
||||
status: playerStatus.IDLE,
|
||||
display: null,
|
||||
setStatus(status: PlayerStatus) {
|
||||
set((s) => {
|
||||
s.status = status;
|
||||
@ -42,26 +38,4 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
||||
};
|
||||
});
|
||||
},
|
||||
setDisplay(newDisplay: DisplayInterface) {
|
||||
const display = get().display;
|
||||
if (display) display.destroy();
|
||||
|
||||
// make display events update the state
|
||||
newDisplay.on("pause", () =>
|
||||
set((s) => {
|
||||
s.mediaPlaying.isPaused = true;
|
||||
s.mediaPlaying.isPlaying = false;
|
||||
})
|
||||
);
|
||||
newDisplay.on("play", () =>
|
||||
set((s) => {
|
||||
s.mediaPlaying.isPaused = false;
|
||||
s.mediaPlaying.isPlaying = true;
|
||||
})
|
||||
);
|
||||
|
||||
set((s) => {
|
||||
s.display = newDisplay;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { StateCreator } from "zustand";
|
||||
|
||||
import { DisplaySlice } from "@/stores/player/slices/display";
|
||||
import { InterfaceSlice } from "@/stores/player/slices/interface";
|
||||
import { PlayingSlice } from "@/stores/player/slices/playing";
|
||||
import { ProgressSlice } from "@/stores/player/slices/progress";
|
||||
@ -8,7 +9,8 @@ import { SourceSlice } from "@/stores/player/slices/source";
|
||||
export type AllSlices = InterfaceSlice &
|
||||
PlayingSlice &
|
||||
ProgressSlice &
|
||||
SourceSlice;
|
||||
SourceSlice &
|
||||
DisplaySlice;
|
||||
export type MakeSlice<Slice> = StateCreator<
|
||||
AllSlices,
|
||||
[["zustand/immer", never]],
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { create } from "zustand";
|
||||
import { immer } from "zustand/middleware/immer";
|
||||
|
||||
import { createDisplaySlice } from "@/stores/player/slices/display";
|
||||
import { createInterfaceSlice } from "@/stores/player/slices/interface";
|
||||
import { createPlayingSlice } from "@/stores/player/slices/playing";
|
||||
import { createProgressSlice } from "@/stores/player/slices/progress";
|
||||
@ -13,5 +14,6 @@ export const usePlayerStore = create(
|
||||
...createProgressSlice(...a),
|
||||
...createPlayingSlice(...a),
|
||||
...createSourceSlice(...a),
|
||||
...createDisplaySlice(...a),
|
||||
}))
|
||||
);
|
||||
|
@ -26,23 +26,23 @@ module.exports = {
|
||||
"ash-400": "#3D394D",
|
||||
"ash-300": "#2C293A",
|
||||
"ash-200": "#2B2836",
|
||||
"ash-100": "#1E1C26",
|
||||
"ash-100": "#1E1C26"
|
||||
},
|
||||
|
||||
/* fonts */
|
||||
fontFamily: {
|
||||
"open-sans": "'Open Sans'",
|
||||
"open-sans": "'Open Sans'"
|
||||
},
|
||||
|
||||
/* animations */
|
||||
keyframes: {
|
||||
"loading-pin": {
|
||||
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
||||
"20%": { height: "1em", "background-color": "white" },
|
||||
},
|
||||
"20%": { height: "1em", "background-color": "white" }
|
||||
}
|
||||
},
|
||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" },
|
||||
},
|
||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
require("tailwind-scrollbar"),
|
||||
@ -52,25 +52,25 @@ module.exports = {
|
||||
colors: {
|
||||
// Branding
|
||||
pill: {
|
||||
background: "#1C1C36",
|
||||
background: "#1C1C36"
|
||||
},
|
||||
|
||||
// meta data for the theme itself
|
||||
global: {
|
||||
accentA: "#505DBD",
|
||||
accentB: "#3440A1",
|
||||
accentB: "#3440A1"
|
||||
},
|
||||
|
||||
// light bar
|
||||
lightBar: {
|
||||
light: "#2A2A71",
|
||||
light: "#2A2A71"
|
||||
},
|
||||
|
||||
// only used for body colors/textures
|
||||
background: {
|
||||
main: "#0A0A10",
|
||||
accentA: "#6E3B80",
|
||||
accentB: "#1F1F50",
|
||||
accentB: "#1F1F50"
|
||||
},
|
||||
|
||||
// typography
|
||||
@ -78,7 +78,7 @@ module.exports = {
|
||||
emphasis: "#FFFFFF",
|
||||
text: "#73739D",
|
||||
dimmed: "#926CAD",
|
||||
divider: "#262632",
|
||||
divider: "#262632"
|
||||
},
|
||||
|
||||
// search bar
|
||||
@ -87,7 +87,7 @@ module.exports = {
|
||||
focused: "#24243C",
|
||||
placeholder: "#4A4A71",
|
||||
icon: "#545476",
|
||||
text: "#FFFFFF",
|
||||
text: "#FFFFFF"
|
||||
},
|
||||
|
||||
// media cards
|
||||
@ -99,11 +99,16 @@ module.exports = {
|
||||
barColor: "#4B4B63",
|
||||
barFillColor: "#BA7FD6",
|
||||
badge: "#151522",
|
||||
badgeText: "#5F5F7A",
|
||||
badgeText: "#5F5F7A"
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
// video player
|
||||
video: {
|
||||
buttonBackground: "#444B5C"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user