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
Show file tree
Hide file tree
Changes from all commits
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
Binary file modified bun.lockb
Binary file not shown.
61 changes: 61 additions & 0 deletions fixtures/media-chrome/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"
>
<style>
media-controller {
display: block;
width: 100%;
aspect-ratio: 16 / 9;
background: #000;
}

media-controller[interstitial="1"] media-time-range {
display: none;
}
</style>
</head>
<body>
<script async src="https://cdn.jsdelivr.net/npm/es-module-shims"></script>
<script type="importmap">
{
"imports": {
"super-media-element": "https://cdn.jsdelivr.net/npm/[email protected]/+esm",
"media-tracks": "https://cdn.jsdelivr.net/npm/[email protected]/+esm",
"@superstreamer/player": "/packages/player/dist/index.js",
"hls.js": "https://cdn.jsdelivr.net/npm/[email protected]/dist/hls.mjs"
}
}
</script>
<script type="module" src="./superstreamer-video-element.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/media-chrome/+esm"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/media-chrome/menu/+esm"></script>

<media-controller>
<superstreamer-video
slot="media"
src="https://stitcher.superstreamer.xyz/session/6c127f2a-ca33-4eca-aff2-29ab3c3aac7c/master.m3u8">
</superstreamer-video>
<media-loading-indicator slot="centered-chrome" noautohide></media-loading-indicator>
<media-rendition-menu hidden anchor="auto"></media-rendition-menu>
<media-audio-track-menu hidden anchor="auto"></media-audio-track-menu>
<media-captions-menu hidden anchor="auto"></media-captions-menu>
<media-control-bar>
<media-play-button></media-play-button>
<media-seek-forward-button></media-seek-forward-button>
<media-mute-button></media-mute-button>
<media-volume-range></media-volume-range>
<media-time-range></media-time-range>
<media-time-display showduration></media-time-display>
<media-rendition-menu-button></media-rendition-menu-button>
<media-audio-track-menu-button></media-audio-track-menu-button>
<media-captions-menu-button></media-captions-menu-button>
<media-fullscreen-button></media-fullscreen-button>
</media-control-bar>
</media-controller>
</body>
</html>
283 changes: 283 additions & 0 deletions fixtures/media-chrome/superstreamer-video-element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
import { Events, HlsPlayer } from "@superstreamer/player";
import { MediaTracksMixin } from "media-tracks";

function getTemplateHTML() {
return `
<style>
:host {
width: 100%;
height: 100%;
}
</style>
<div class="container"></div>
`;
}

const symbolTrackId_ = Symbol("superstreamer.trackId");

class SuperstreamerVideoElement extends MediaTracksMixin(
globalThis.HTMLElement,
) {
static getTemplateHTML = getTemplateHTML;

static shadowRootOptions = {
mode: "open",
};

static observedAttributes = ["src"];

#player;

#readyState = 0;

#video;

constructor() {
super();

if (!this.shadowRoot) {
this.attachShadow({
mode: "open",
});
this.shadowRoot.innerHTML = getTemplateHTML();
}

const container = this.shadowRoot.querySelector(".container");
this.#player = new HlsPlayer(container);

this.#video = document.createElement("video");

this.#bindListeners();
}

#bindListeners() {
this.#player.on(Events.PLAYHEAD_CHANGE, () => {
switch (this.#player.playhead) {
case "play":
this.dispatchEvent(new Event("play"));
break;
case "playing":
this.dispatchEvent(new Event("playing"));
break;
case "pause":
this.dispatchEvent(new Event("pause"));
break;
}
});

this.#player.on(Events.TIME_CHANGE, () => {
this.dispatchEvent(new Event("timeupdate"));
});

this.#player.on(Events.VOLUME_CHANGE, () => {
this.dispatchEvent(new Event("volumechange"));
});

this.#player.on(Events.SEEKING_CHANGE, () => {
if (this.#player.seeking) {
this.dispatchEvent(new Event("seeking"));
} else {
this.dispatchEvent(new Event("seeked"));
}
});

this.#player.on(Events.READY, async () => {
this.#readyState = 1;

this.dispatchEvent(new Event("loadedmetadata"));
this.dispatchEvent(new Event("durationchange"));
this.dispatchEvent(new Event("volumechange"));
this.dispatchEvent(new Event("loadcomplete"));

this.#createVideoTracks();
this.#createAudioTracks();
this.#createTextTracks();
});

this.#player.on(Events.STARTED, () => {
this.#readyState = 3;
});

this.#player.on(Events.ASSET_CHANGE, () => {
const controller = this.closest("media-controller");
if (controller) {
controller.setAttribute("interstitial", this.#player.asset ? "1" : "0");
}
});
}

get src() {
return this.getAttribute("src");
}

set src(val) {
if (this.src === val) {
return;
}
this.setAttribute("src", val);
}

get textTracks() {
return this.#video.textTracks;
}

attributeChangedCallback(attrName, oldValue, newValue) {
if (attrName === "src" && oldValue !== newValue) {
this.load();
}
}

async load() {
this.#readyState = 0;

while (this.#video.firstChild) {
this.#video.firstChild.remove();
}

for (const videoTrack of this.videoTracks) {
this.removeVideoTrack(videoTrack);
}
for (const audioTrack of this.audioTracks) {
this.removeAudioTrack(audioTrack);
}

this.#player.unload();

this.dispatchEvent(new Event("emptied"));

if (this.src) {
this.dispatchEvent(new Event("loadstart"));
this.#player.load(this.src);
}
}

get currentTime() {
return this.#player.time;
}

set currentTime(val) {
this.#player.seekTo(val);
}

get duration() {
return this.#player.duration;
}

get paused() {
const { playhead } = this.#player;
if (playhead === "play" || playhead === "playing") {
return false;
}
return true;
}

get readyState() {
return this.#readyState;
}

get muted() {
return this.#player.volume === 0;
}

set muted(val) {
this.#player.setVolume(val ? 0 : 1);
}

get volume() {
return this.#player.volume;
}

set volume(val) {
this.#player.setVolume(val);
}

async play() {
this.#player.playOrPause();
await Promise.resolve();
}

pause() {
this.#player.playOrPause();
}

#createVideoTracks() {
let videoTrack = this.videoTracks.getTrackById("main");

if (!videoTrack) {
videoTrack = this.addVideoTrack("main");
videoTrack.id = "main";
videoTrack.selected = true;
}

this.#player.qualities.forEach((quality) => {
videoTrack.addRendition(
undefined,
quality.height,
quality.height,
undefined,
undefined,
);
});

this.videoRenditions.addEventListener("change", (event) => {
if (event.target.selectedIndex < 0) {
this.#player.setQuality(null);
} else {
const rendition = this.videoRenditions[event.target.selectedIndex];
this.#player.setQuality(rendition.height);
}
});
}

#createAudioTracks() {
this.#player.audioTracks.forEach((a) => {
const audioTrack = this.addAudioTrack("main", a.label, a.label);
audioTrack[symbolTrackId_] = a.id;
audioTrack.enabled = a.active;
});

this.audioTracks.addEventListener("change", () => {
const track = [...this.audioTracks].find((a) => a.enabled);
if (track) {
const id = track[symbolTrackId_];
this.#player.setAudioTrack(id);
}
});
}

#createTextTracks() {
this.#player.subtitleTracks.forEach((s) => {
const textTrack = this.addTextTrack("subtitles", s.label, s.track.lang);
textTrack[symbolTrackId_] = s.id;
});

this.textTracks.addEventListener("change", () => {
const track = [...this.textTracks].find((t) => t.mode === "showing");
if (track) {
const id = track[symbolTrackId_];
this.#player.setSubtitleTrack(id);
} else {
this.#player.setSubtitleTrack(null);
}
});
}

addTextTrack(kind, label, language) {
const trackEl = document.createElement("track");
trackEl.kind = kind;
trackEl.label = label;
trackEl.srclang = language;
trackEl.track.mode = "hidden";
this.#video.append(trackEl);
return trackEl.track;
}
}

if (!globalThis.customElements?.get("superstreamer-video")) {
globalThis.customElements.define(
"superstreamer-video",
SuperstreamerVideoElement,
);
}

export default SuperstreamerVideoElement;
1 change: 1 addition & 0 deletions packages/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Superstreamer</title>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="root"></div>
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ export function CodeEditor({

return (
<MonacoEditor
className="h-full w-full"
defaultLanguage="json"
defaultValue={value}
onMount={onMount}
Expand Down
22 changes: 0 additions & 22 deletions packages/app/src/components/DataDump.tsx

This file was deleted.

Loading
Loading