Skip to content

Commit

Permalink
player
Browse files Browse the repository at this point in the history
  • Loading branch information
matvp91 committed Dec 3, 2024
1 parent 866dc84 commit 39c10a7
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 2 deletions.
28 changes: 28 additions & 0 deletions packages/app/src/components/PlayerTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { HlsPlayer } from "@superstreamer/player/player";
import { useEffect, useRef, useState } from "react";

interface PlayerTestProps {
url?: string | null;
}

export function PlayerTest({ url }: PlayerTestProps) {
const ref = useRef<HTMLDivElement>(null);
const [player, setPlayer] = useState<HlsPlayer | null>(null);

useEffect(() => {
const player = new HlsPlayer(ref.current!);
setPlayer(player);
}, []);

useEffect(() => {
if (!player || !url) {
return;
}
player.load(url);
return () => {
player.reset();
};
}, [player, url]);

return <div className="relative aspect-video bg-black" ref={ref} />;
}
4 changes: 2 additions & 2 deletions packages/app/src/routes/(dashboard)/_layout/player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createFileRoute } from "@tanstack/react-router";
import { useRef, useState } from "react";
import { CodeEditor } from "../../../components/CodeEditor";
import { Form } from "../../../components/Form";
import { Player } from "../../../components/Player";
import { PlayerTest } from "../../../components/PlayerTest";
import { useSwaggerSchema } from "../../../hooks/useSwaggerSchema";
import type { FormRef } from "../../../components/Form";

Expand All @@ -24,7 +24,7 @@ function RouteComponent() {
return (
<div className="h-screen p-8 flex gap-4">
<div className="grow">
<Player url={url} lang="eng" metadata={{}} />
<PlayerTest url={url} />
<Card className="mt-4 p-4">
<Form
ref={formRef}
Expand Down
4 changes: 4 additions & 0 deletions packages/player/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"./react": {
"types": "./dist/react.d.ts",
"default": "./dist/react.js"
},
"./player": {
"types": "./dist/player.d.ts",
"default": "./dist/player.js"
}
},
"files": [
Expand Down
111 changes: 111 additions & 0 deletions packages/player/src/player/event-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
interface Target {
addEventListener?: Handler;
removeEventListener?: Handler;
on?: Handler;
off?: Handler;
}

type AddCallback<T extends Target> = T extends { addEventListener: Handler }
? T["addEventListener"]
: T extends { on: Handler }
? T["on"]
: Handler;

type RemoveCallback<T extends Target> = T extends {
removeEventListener: Handler;
}
? T["removeEventListener"]
: T extends { off: Handler }
? T["off"]
: Handler;

export class EventManager {
private bindings_ = new Set<Binding>();

listen = <T extends Target>(target: T) =>
((type, listener, context) => {
const binding = createBinding(target, type, listener, context);
this.bindings_.add(binding);
}) as AddCallback<T>;

listenOnce = <T extends Target>(target: T) =>
((type, listener, context) => {
const binding = createBinding(target, type, listener, context, true);
this.bindings_.add(binding);
}) as AddCallback<T>;

unlisten = <T extends Target>(target: T) =>
((type, listener) => {
const binding = Array.from(this.bindings_).find(
(binding) =>
binding.target === target &&
binding.type === type &&
binding.listener === listener,
);
if (binding) {
binding.remove();
this.bindings_.delete(binding);
}
}) as RemoveCallback<T>;

removeAll() {
this.bindings_.forEach((binding) => {
binding.remove();
});
this.bindings_.clear();
}
}

/**
* Create a binding for a specific target.
* @param target
* @param type
* @param listener
* @param context
* @param once
* @returns
*/
function createBinding(
target: Target,
type: string,
listener: Handler,
context?: unknown,
once?: boolean,
) {
const methodMap = {
add: target.addEventListener?.bind(target) ?? target.on?.bind(target),
remove:
target.removeEventListener?.bind(target) ?? target.off?.bind(target),
};

const remove = () => {
methodMap.remove?.(type, callback);
};

const callback = async (...args: unknown[]) => {
try {
await listener.apply(context, args);
if (once) {
remove();
}
} catch (error) {
console.error(error);
}
};

methodMap.add?.(type, callback);

return {
target,
type,
listener,
context,
once,
remove,
};
}

type Binding = ReturnType<typeof createBinding>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Handler = (...args: any) => any;
72 changes: 72 additions & 0 deletions packages/player/src/player/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import Hls from "hls.js";
import { EventManager } from "./event-manager";

export class HlsPlayer {
private media_: HTMLMediaElement;

private assetMedias_: [HTMLMediaElement, HTMLMediaElement];

private hlsMap_ = new Map<HTMLMediaElement, Hls>();

private eventManager_ = new EventManager();

constructor(public container: HTMLDivElement) {
this.media_ = this.createMedia_();

this.assetMedias_ = [this.createMedia_(), this.createMedia_()];

this.setActiveMedia_(this.media_);
}

private createMedia_() {
const media = document.createElement("video");
this.container.appendChild(media);

media.style.position = "absolute";
media.style.inset = "0";
media.style.width = "100%";
media.style.height = "100%";

return media;
}

load(url: string) {
const hls = new Hls();
hls.attachMedia(this.media_);

this.hlsMap_.set(this.media_, hls);

this.bindListeners_(hls);

hls.loadSource(url);
}

reset() {
this.eventManager_.removeAll();

const hls = this.hlsMap_.get(this.media_);
if (hls) {
hls.destroy();
this.hlsMap_.delete(this.media_);
}
}

private bindListeners_(hls: Hls) {
const listen = this.eventManager_.listen(hls);

listen(Hls.Events.MANIFEST_LOADED, () => {
console.log("LOADED IT");
});

listen(Hls.Events.INTERSTITIAL_ASSET_PLAYER_CREATED, (event) => {});

listen(Hls.Events.INTERSTITIAL_ASSET_STARTED, () => {});
}

private setActiveMedia_(media: HTMLMediaElement) {
const allMedias = [this.media_, ...this.assetMedias_];
allMedias.forEach((element) => {
element.style.opacity = element === media ? "1" : "0";
});
}
}
1 change: 1 addition & 0 deletions packages/player/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default defineConfig({
entry: {
index: "./src/facade/index.ts",
react: "./src/react/index.tsx",
player: "./src/player/index.ts",
},
splitting: false,
sourcemap: true,
Expand Down

0 comments on commit 39c10a7

Please sign in to comment.