diff --git a/package.json b/package.json
index 0c5fb382..fa2d0283 100644
--- a/package.json
+++ b/package.json
@@ -101,6 +101,7 @@
"tailwind-scrollbar": "^2.0.1",
"tailwindcss": "^3.2.4",
"tailwindcss-themer": "^3.1.0",
+ "type-fest": "^4.3.3",
"typescript": "^4.6.4",
"vite": "^4.0.1",
"vite-plugin-checker": "^0.5.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ba43a93d..05eba9b1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -229,6 +229,9 @@ devDependencies:
tailwindcss-themer:
specifier: ^3.1.0
version: 3.1.0(tailwindcss@3.3.3)
+ type-fest:
+ specifier: ^4.3.3
+ version: 4.3.3
typescript:
specifier: ^4.6.4
version: 4.9.5
@@ -6100,6 +6103,11 @@ packages:
engines: {node: '>=10'}
dev: true
+ /type-fest@4.3.3:
+ resolution: {integrity: sha512-bxhiFii6BBv6UiSDq7uKTMyADT9unXEl3ydGefndVLxFeB44LRbT4K7OJGDYSyDrKnklCC1Pre68qT2wbUl2Aw==}
+ engines: {node: '>=16'}
+ dev: true
+
/typed-array-buffer@1.0.0:
resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
engines: {node: '>= 0.4'}
diff --git a/src/components/player/Player.tsx b/src/components/player/Player.tsx
index 74407269..c9aad803 100644
--- a/src/components/player/Player.tsx
+++ b/src/components/player/Player.tsx
@@ -1,6 +1,7 @@
export * from "./atoms";
export * from "./base/Container";
export * from "./base/TopControls";
+export * from "./base/CenterControls";
export * from "./base/BottomControls";
export * from "./base/BlackOverlay";
export * from "./base/BackLink";
diff --git a/src/components/player/atoms/AutoPlayStart.tsx b/src/components/player/atoms/AutoPlayStart.tsx
new file mode 100644
index 00000000..a0019391
--- /dev/null
+++ b/src/components/player/atoms/AutoPlayStart.tsx
@@ -0,0 +1,34 @@
+import { useCallback } from "react";
+
+import { Icon, Icons } from "@/components/Icon";
+import { playerStatus } from "@/stores/player/slices/source";
+import { usePlayerStore } from "@/stores/player/store";
+
+export function AutoPlayStart() {
+ const display = usePlayerStore((s) => s.display);
+ const isPlaying = usePlayerStore((s) => s.mediaPlaying.isPlaying);
+ const isLoading = usePlayerStore((s) => s.mediaPlaying.isLoading);
+ const hasPlayedOnce = usePlayerStore((s) => s.mediaPlaying.hasPlayedOnce);
+ const status = usePlayerStore((s) => s.status);
+
+ const handleClick = useCallback(() => {
+ display?.play();
+ }, [display]);
+
+ if (hasPlayedOnce) return null;
+ if (isPlaying) return null;
+ if (isLoading) return null;
+ if (status !== playerStatus.PLAYING) return null;
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/player/atoms/LoadingSpinner.tsx b/src/components/player/atoms/LoadingSpinner.tsx
new file mode 100644
index 00000000..11060b4f
--- /dev/null
+++ b/src/components/player/atoms/LoadingSpinner.tsx
@@ -0,0 +1,10 @@
+import { Spinner } from "@/components/layout/Spinner";
+import { usePlayerStore } from "@/stores/player/store";
+
+export function LoadingSpinner() {
+ const isLoading = usePlayerStore((s) => s.mediaPlaying.isLoading);
+
+ if (!isLoading) return null;
+
+ return ;
+}
diff --git a/src/components/player/atoms/index.ts b/src/components/player/atoms/index.ts
index f59c0183..0ac37749 100644
--- a/src/components/player/atoms/index.ts
+++ b/src/components/player/atoms/index.ts
@@ -3,3 +3,5 @@ export * from "./Fullscreen";
export * from "./ProgressBar";
export * from "./Skips";
export * from "./Time";
+export * from "./LoadingSpinner";
+export * from "./AutoPlayStart";
diff --git a/src/components/player/base/CenterControls.tsx b/src/components/player/base/CenterControls.tsx
new file mode 100644
index 00000000..478b9234
--- /dev/null
+++ b/src/components/player/base/CenterControls.tsx
@@ -0,0 +1,7 @@
+export function CenterControls(props: { children: React.ReactNode }) {
+ return (
+
+ {props.children}
+
+ );
+}
diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts
index f92bdeee..ef3a5a88 100644
--- a/src/components/player/display/base.ts
+++ b/src/components/player/display/base.ts
@@ -26,8 +26,14 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
function setSource() {
if (!videoElement || !source) return;
videoElement.src = source.url;
- videoElement.addEventListener("play", () => emit("play", undefined));
+ videoElement.addEventListener("play", () => {
+ emit("play", undefined);
+ emit("loading", false);
+ });
+ videoElement.addEventListener("playing", () => emit("play", undefined));
videoElement.addEventListener("pause", () => emit("pause", undefined));
+ videoElement.addEventListener("canplay", () => emit("loading", false));
+ videoElement.addEventListener("waiting", () => emit("loading", true));
videoElement.addEventListener("volumechange", () =>
emit("volumechange", videoElement?.volume ?? 0)
);
@@ -57,10 +63,15 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
on,
off,
destroy: () => {
+ if (videoElement) {
+ videoElement.src = "";
+ videoElement.remove();
+ }
fscreen.removeEventListener("fullscreenchange", fullscreenChange);
},
load(newSource) {
source = newSource;
+ emit("loading", true);
setSource();
},
diff --git a/src/components/player/display/displayInterface.ts b/src/components/player/display/displayInterface.ts
index 6d5e1417..6b037846 100644
--- a/src/components/player/display/displayInterface.ts
+++ b/src/components/player/display/displayInterface.ts
@@ -9,6 +9,7 @@ export type DisplayInterfaceEvents = {
time: number;
duration: number;
buffered: number;
+ loading: boolean;
};
export interface DisplayInterface extends Listener {
diff --git a/src/components/player/internals/VideoContainer.tsx b/src/components/player/internals/VideoContainer.tsx
index 9b6dbe2c..f4b372ac 100644
--- a/src/components/player/internals/VideoContainer.tsx
+++ b/src/components/player/internals/VideoContainer.tsx
@@ -13,6 +13,9 @@ function useDisplayInterface() {
if (!display) {
setDisplay(makeVideoElementDisplayInterface());
}
+ return () => {
+ if (display) setDisplay(null);
+ };
}, [display, setDisplay]);
}
diff --git a/src/pages/PlayerView.tsx b/src/pages/PlayerView.tsx
index 21e98e63..597f9d97 100644
--- a/src/pages/PlayerView.tsx
+++ b/src/pages/PlayerView.tsx
@@ -1,12 +1,14 @@
+import { MWStreamType } from "@/backend/helpers/streams";
import { BrandPill } from "@/components/layout/BrandPill";
import { Player } from "@/components/player";
+import { AutoPlayStart } from "@/components/player/atoms";
import { usePlayer } from "@/components/player/hooks/usePlayer";
import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls";
import { ScrapingPart } from "@/pages/parts/player/ScrapingPart";
import { playerStatus } from "@/stores/player/slices/source";
export function PlayerView() {
- const { status, setScrapeStatus } = usePlayer();
+ const { status, setScrapeStatus, playMedia } = usePlayer();
const desktopControlsVisible = useShouldShowControls();
return (
@@ -15,15 +17,32 @@ export function PlayerView() {
{
+ if (out?.stream.type !== "file") return;
+ const qualities = Object.keys(
+ out.stream.qualities
+ ) as (keyof typeof out.stream.qualities)[];
+ const file = out.stream.qualities[qualities[0]];
+ if (!file) return;
+ playMedia({
+ type: MWStreamType.MP4,
+ url: file.url,
+ });
+ }}
/>
) : null}
+
+
+
+
+
+
@@ -41,6 +60,7 @@ export function PlayerView() {
+
diff --git a/src/pages/parts/player/ScrapingPart.tsx b/src/pages/parts/player/ScrapingPart.tsx
index 8e2cf700..585b2f3b 100644
--- a/src/pages/parts/player/ScrapingPart.tsx
+++ b/src/pages/parts/player/ScrapingPart.tsx
@@ -1,14 +1,14 @@
-import { ScrapeMedia } from "@movie-web/providers";
+import { ProviderControls, ScrapeMedia } from "@movie-web/providers";
import { useCallback, useEffect, useRef, useState } from "react";
+import type { AsyncReturnType } from "type-fest";
-import { MWStreamType } from "@/backend/helpers/streams";
import { usePlayer } from "@/components/player/hooks/usePlayer";
import { StatusCircle } from "@/components/player/internals/StatusCircle";
import { providers } from "@/utils/providers";
export interface ScrapingProps {
media: ScrapeMedia;
- // onGetStream?: () => void;
+ onGetStream?: (stream: AsyncReturnType
) => void;
}
export interface ScrapingSegment {
@@ -30,7 +30,7 @@ function useScrape() {
const startScraping = useCallback(
async (media: ScrapeMedia) => {
- if (!providers) return;
+ if (!providers) return null;
const output = await providers.runAll({
media,
events: {
@@ -118,12 +118,7 @@ export function ScrapingPart(props: ScrapingProps) {
started.current = true;
(async () => {
const output = await startScraping(props.media);
- if (output?.stream.type !== "file") return;
- const firstFile = Object.values(output.stream.qualities)[0];
- playMedia({
- type: MWStreamType.MP4,
- url: firstFile.url,
- });
+ props.onGetStream?.(output);
})();
}, [startScraping, props, playMedia]);
diff --git a/src/stores/player/slices/display.ts b/src/stores/player/slices/display.ts
index c1bbefb9..bb2645bd 100644
--- a/src/stores/player/slices/display.ts
+++ b/src/stores/player/slices/display.ts
@@ -3,15 +3,22 @@ import { MakeSlice } from "@/stores/player/slices/types";
export interface DisplaySlice {
display: DisplayInterface | null;
- setDisplay(display: DisplayInterface): void;
+ setDisplay(display: DisplayInterface | null): void;
}
export const createDisplaySlice: MakeSlice = (set, get) => ({
display: null,
- setDisplay(newDisplay: DisplayInterface) {
+ setDisplay(newDisplay: DisplayInterface | null) {
const display = get().display;
if (display) display.destroy();
+ if (!newDisplay) {
+ set((s) => {
+ s.display = null;
+ });
+ return;
+ }
+
// make display events update the state
newDisplay.on("pause", () =>
set((s) => {
@@ -21,6 +28,7 @@ export const createDisplaySlice: MakeSlice = (set, get) => ({
);
newDisplay.on("play", () =>
set((s) => {
+ s.mediaPlaying.hasPlayedOnce = true;
s.mediaPlaying.isPaused = false;
s.mediaPlaying.isPlaying = true;
})
@@ -50,6 +58,11 @@ export const createDisplaySlice: MakeSlice = (set, get) => ({
s.progress.buffered = buffered;
})
);
+ newDisplay.on("loading", (isLoading) =>
+ set((s) => {
+ s.mediaPlaying.isLoading = isLoading;
+ })
+ );
set((s) => {
s.display = newDisplay;
diff --git a/src/stores/player/slices/playing.ts b/src/stores/player/slices/playing.ts
index 28e6f5a6..26d6f2dd 100644
--- a/src/stores/player/slices/playing.ts
+++ b/src/stores/player/slices/playing.ts
@@ -7,7 +7,6 @@ export interface PlayingSlice {
isSeeking: boolean; // seeking with progress bar
isDragSeeking: boolean; // is seeking for our custom progress bar
isLoading: boolean; // buffering or not
- isFirstLoading: boolean; // first buffering of the video, when set to false the video can start playing
hasPlayedOnce: boolean; // has the video played at all?
volume: number;
playbackSpeed: number;