Skip to content

Commit

Permalink
Multiple video elements
Browse files Browse the repository at this point in the history
  • Loading branch information
matvp91 committed Dec 5, 2024
1 parent a68b72c commit 01c553c
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 23 deletions.
6 changes: 3 additions & 3 deletions packages/player/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@
"scripts": {
"dev": "tsup --watch",
"build": "tsc && tsup",
"lint": "tsc && eslint",
"lint": "tsc && eslint",
"typedoc": "typedoc --tsconfig tsconfig.typedoc.json"
},
"peerDependencies": {
"hls.js": "^1.6.0-beta.1",
"react": "^18.3.1"
},
"dependencies": {
"clsx": "^2.1.1",
"eventemitter3": "^5.0.1",
"screenfull": "^6.0.2",
"use-sync-external-store": "^1.2.2",
"zustand": "^5.0.0"
"zustand": "^5.0.0",
"hls.js": "^1.6.0-beta.1"
},
"devDependencies": {
"@types/eventemitter3": "^2.0.2",
Expand Down
111 changes: 91 additions & 20 deletions packages/player/src/player/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
import Hls from "hls.js";
import { EventManager } from "./event-manager";
import { State } from "./state";

export class HlsPlayer {
private media_: HTMLMediaElement;
private primaryMedia_: HTMLMediaElement;

private assetMedias_: [HTMLMediaElement, HTMLMediaElement];

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

private eventManager_ = new EventManager();

private activeMedia_: HTMLMediaElement;

private hls_: Hls | null = null;

private state_: State | null = null;

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

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

this.setActiveMedia_(this.media_);
this.allMedias_.forEach((media) => {
this.addMediaListeners_(media);
});

this.setActiveMedia_(this.primaryMedia_);

// Temporary, for debug purposes.
Object.assign(window, {
player: this,
});
}

private createMedia_() {
Expand All @@ -31,42 +47,97 @@ export class HlsPlayer {
}

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

this.hlsMap_.set(this.media_, hls);
const hls = this.createHls_();

this.bindListeners_(hls);
this.state_ = new State({
getTiming() {
const { media } = hls;
return media ? [media.currentTime, media.duration] : null;
},
});

hls.attachMedia(this.primaryMedia_);
hls.loadSource(url);

this.hls_ = hls;
}

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

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

private bindListeners_(hls: Hls) {
private createHls_() {
const hls = new Hls();

const listen = this.eventManager_.listen(hls);

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

listen(Hls.Events.INTERSTITIAL_ASSET_PLAYER_CREATED, (event) => {});
listen(Hls.Events.INTERSTITIAL_ASSET_STARTED, (_, data) => {
const media = this.claimMedia_(false);
data.player.attachMedia(media);
});

listen(Hls.Events.INTERSTITIAL_ASSET_STARTED, () => {});
listen(Hls.Events.INTERSTITIALS_PRIMARY_RESUMED, () => {
this.claimMedia_(true);
});

return hls;
}

private claimMedia_(primary: boolean) {
let media = this.primaryMedia_;

if (!primary) {
const idx = this.assetMediaIndex_;
media = this.assetMedias_[idx];
this.assetMediaIndex_ = (idx + 1) % this.assetMedias_.length;
}

this.setActiveMedia_(media);

return media;
}

private setActiveMedia_(media: HTMLMediaElement) {
const allMedias = [this.media_, ...this.assetMedias_];
allMedias.forEach((element) => {
this.allMedias_.forEach((element) => {
element.style.opacity = element === media ? "1" : "0";
});

this.activeMedia_ = media;
}

private get allMedias_() {
return [this.primaryMedia_, ...this.assetMedias_];
}

private addMediaListeners_(media: HTMLMediaElement) {
const listen = this.eventManager_.listen(media);

const isActive = () => this.activeMedia_ === media;

listen("canplay", () => {
this.state_?.setReady();
});

listen("playing", () => {
this.state_?.setStarted();
if (isActive()) {
this.state_?.setPlayhead("playing");
}
});

listen("pause", () => {
if (isActive()) {
this.state_?.setPlayhead("pause");
}
});
}
}
56 changes: 56 additions & 0 deletions packages/player/src/player/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Playhead } from "./types";

interface StateParams {
getTiming(): null | [number, number];
}

export class State {
playhead: Playhead = "idle";

ready = false;

started = false;

private timerId_: number | undefined;

constructor(private params_: StateParams) {
this.requestTimingSync();
}

setReady() {
if (this.ready) {
return;
}
this.ready = true;
this.requestTimingSync();
}

setPlayhead(playhead: Playhead) {
if (playhead === this.playhead) {
return;
}
this.playhead = playhead;
}

setStarted() {
if (this.started) {
return;
}
this.started = true;
}

requestTimingSync() {
clearTimeout(this.timerId_);
this.timerId_ = window.setTimeout(() => {
this.requestTimingSync();
}, 250);

const timing = this.params_.getTiming();
if (!timing) {
return;
}

const [time, duration] = timing;
console.log(time, duration);
}
}
1 change: 1 addition & 0 deletions packages/player/src/player/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Playhead = "idle" | "play" | "playing" | "pause" | "ended";

0 comments on commit 01c553c

Please sign in to comment.