Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Player #135

Merged
merged 31 commits into from
Dec 19, 2024
Merged
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
39c10a7
player
matvp91 Dec 3, 2024
a68b72c
Merge branch 'main' of github.com:matvp91/mixwave into feature/player…
matvp91 Dec 5, 2024
01c553c
Multiple video elements
matvp91 Dec 5, 2024
ca3e053
New player
matvp91 Dec 6, 2024
98e094b
Fixture for media-chrome
matvp91 Dec 6, 2024
45dde29
Prettier
matvp91 Dec 6, 2024
d69fdf1
Rendition menu
matvp91 Dec 6, 2024
c464967
Added mapping for audio and quality
matvp91 Dec 6, 2024
df5fe0d
Added expiry
matvp91 Dec 7, 2024
cbebc18
Removed unused log
matvp91 Dec 7, 2024
f6a7e6a
Added temp code
matvp91 Dec 9, 2024
fa5d6ac
Added textTracks support
matvp91 Dec 9, 2024
c99d615
Ignore error
matvp91 Dec 9, 2024
0e92824
Interstitial demo asset
matvp91 Dec 9, 2024
621f243
Fix tags
matvp91 Dec 10, 2024
8ef5324
Seeking
matvp91 Dec 10, 2024
61b32ae
Added support for multiple load calls
matvp91 Dec 10, 2024
bfd4a8d
Added interstitial asset
matvp91 Dec 10, 2024
c8f7c6d
Hide seekbar
matvp91 Dec 10, 2024
03ab89c
Merge branch 'main' of github.com:matvp91/mixwave into feature/player…
matvp91 Dec 10, 2024
a0f4ae7
Added object dump
matvp91 Dec 10, 2024
73fc260
Added context viewer
matvp91 Dec 11, 2024
f2cf130
Added json viewer
matvp91 Dec 11, 2024
da06384
Support for text files
matvp91 Dec 11, 2024
158412f
Merge branch 'main' of github.com:matvp91/mixwave into feature/player…
matvp91 Dec 14, 2024
197018c
Added interstitial instead of solely asset
matvp91 Dec 14, 2024
b58975a
Added player controls
matvp91 Dec 17, 2024
930d5fb
Added controls to bottom
matvp91 Dec 17, 2024
62a4146
Show cuepoints
matvp91 Dec 17, 2024
bb23744
Added player view
matvp91 Dec 17, 2024
b18b0b1
Added more player
matvp91 Dec 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
player
  • Loading branch information
matvp91 committed Dec 3, 2024
commit 39c10a7bcb656f1d62fe5b38dbe8e960aeac9dd3
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
@@ -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";

@@ -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}
4 changes: 4 additions & 0 deletions packages/player/package.json
Original file line number Diff line number Diff line change
@@ -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": [
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
@@ -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,
Loading