This commit is contained in:
Luke Curley 2023-04-01 18:34:10 -07:00
parent 032c49ab50
commit c8c856d6b8
11 changed files with 44 additions and 30 deletions

7
player/.proxyrc.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = function (app) {
app.use((req, res, next) => {
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
next();
});
};

View File

@ -8,6 +8,7 @@
"devDependencies": { "devDependencies": {
"@parcel/transformer-inline-string": "2.8.3", "@parcel/transformer-inline-string": "2.8.3",
"@parcel/validator-typescript": "^2.6.0", "@parcel/validator-typescript": "^2.6.0",
"@types/audioworklet": "^0.0.41",
"@types/dom-webcodecs": "^0.1.6", "@types/dom-webcodecs": "^0.1.6",
"parcel": "^2.8.0", "parcel": "^2.8.0",
"typescript": ">=3.0.0" "typescript": ">=3.0.0"

View File

@ -10,7 +10,7 @@ export class Decoder {
tracks: Map<string, Util.Deferred<Message.Init>> tracks: Map<string, Util.Deferred<Message.Init>>
renderer: Renderer; renderer: Renderer;
constructor(renderer: Renderer) { constructor(config: Message.Config, renderer: Renderer) {
this.tracks = new Map(); this.tracks = new Map();
this.renderer = renderer; this.renderer = renderer;
} }
@ -22,6 +22,7 @@ export class Decoder {
this.tracks.set(msg.track, track) this.tracks.set(msg.track, track)
} }
console.log(msg.info)
if (msg.info.audioTracks.length != 1 || msg.info.videoTracks.length != 0) { if (msg.info.audioTracks.length != 1 || msg.info.videoTracks.length != 0) {
throw new Error("Expected a single audio track") throw new Error("Expected a single audio track")
} }
@ -42,16 +43,19 @@ export class Decoder {
const audio = info.audioTracks[0] const audio = info.audioTracks[0]
const decoder = new AudioDecoder({ const decoder = new AudioDecoder({
output: (frame: AudioFrame) => { output: (frame: AudioData) => {
this.renderer.emit(frame) this.renderer.emit(frame)
}, },
error: (err: Error) => { error: (err: Error) => {
console.warn(err) console.warn(err)
} }
}); });
console.log(audio)
decoder.configure({ decoder.configure({
codec: audio.codec, codec: audio.codec,
numberOfChannels: audio.audio.channel_count,
sampleRate: audio.audio.sample_rate,
// optimizeForLatency: true // optimizeForLatency: true
}) })
@ -61,6 +65,7 @@ export class Decoder {
for (let sample of samples) { for (let sample of samples) {
// TODO this assumes that timescale == sample rate // TODO this assumes that timescale == sample rate
decoder.decode(new EncodedAudioChunk({ decoder.decode(new EncodedAudioChunk({
type: sample.is_sync ? "key" : "delta",
data: sample.data, data: sample.data,
duration: sample.duration, duration: sample.duration,
timestamp: sample.dts, timestamp: sample.dts,

View File

@ -1,19 +1,21 @@
import * as Message from "./message" import * as Message from "./message"
import { Renderer } from "./renderer"
import { Decoder } from "./decoder"
// Wrapper around the WebWorker API
export default class Audio { export default class Audio {
worker: Worker; renderer: Renderer;
decoder: Decoder;
constructor(config: Message.Config) { constructor(config: Message.Config) {
this.worker = new Worker(new URL('worker.ts', import.meta.url), { type: "module" }) this.renderer = new Renderer(config)
this.worker.postMessage({ config }, []) this.decoder = new Decoder(config, this.renderer)
} }
init(init: Message.Init) { async init(init: Message.Init) {
this.worker.postMessage({ init }) // note: we copy the raw init bytes each time await this.decoder.init(init)
} }
segment(segment: Message.Segment) { async segment(segment: Message.Segment) {
this.worker.postMessage({ segment }, [ segment.buffer.buffer, segment.reader ]) await this.decoder.decode(segment)
} }
} }

View File

@ -1,7 +1,7 @@
import * as MP4 from "../mp4" import * as MP4 from "../mp4"
export interface Config { export interface Config {
// temporarily empty ctx: AudioContext;
} }
export interface Init { export interface Init {

View File

@ -3,7 +3,6 @@ import * as Message from "./message";
import Source from "./source"; import Source from "./source";
export class Renderer { export class Renderer {
ctx: AudioContext;
source: Source; source: Source;
render: number; // non-zero if requestAnimationFrame has been called render: number; // non-zero if requestAnimationFrame has been called
@ -11,16 +10,15 @@ export class Renderer {
maxDuration: number; // the maximum duration allowed in the buffer maxDuration: number; // the maximum duration allowed in the buffer
constructor() { constructor(config: Message.Config) {
this.render = 0; this.render = 0;
this.maxDuration = 10 * 1000 this.maxDuration = 10 * 1000
// TODO evaluate { latencyHint: "interactive" } // TODO evaluate { latencyHint: "interactive" }
this.ctx = new AudioContext() this.source = new Source(config.ctx)
this.source = new Source(this.ctx)
} }
emit(frame: AudioFrame) { emit(frame: AudioData) {
this.source.emit(frame) this.source.emit(frame)
} }
} }

View File

@ -55,7 +55,7 @@ export default class Source {
} }
} }
emit(frame: AudioFrame) { emit(frame: AudioData) {
for (let i = 0; i < frame.channels; i += 1) { for (let i = 0; i < frame.channels; i += 1) {
const ring = this.channels[i] const ring = this.channels[i]
ring.set(frame, i) ring.set(frame, i)

View File

@ -9,8 +9,8 @@ self.addEventListener('message', async (e: MessageEvent) => {
if (e.data.config) { if (e.data.config) {
const config = e.data.config as Message.Config; const config = e.data.config as Message.Config;
renderer = new Renderer() renderer = new Renderer(config)
decoder = new Decoder(renderer) decoder = new Decoder(config, renderer)
} }
if (e.data.init) { if (e.data.init) {

View File

@ -29,12 +29,6 @@ body {
z-index: 1; z-index: 1;
} }
#vid {
width: 100%;
height: 100%;
max-height: 100vh;
}
#controls { #controls {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@ -27,7 +27,9 @@ export class Player {
this.tracks = new Map(); this.tracks = new Map();
// TODO move these to another class so this only deals with the transport. // TODO move these to another class so this only deals with the transport.
this.audio = new Audio({}) this.audio = new Audio({
ctx: new AudioContext(),
})
this.video = new Video({ this.video = new Video({
canvas: props.canvas.transferControlToOffscreen(), canvas: props.canvas.transferControlToOffscreen(),
}) })
@ -133,13 +135,13 @@ export class Player {
throw new Error("expected a single track") throw new Error("expected a single track")
} }
if (info.audioTracks) { if (info.audioTracks.length) {
this.audio.init({ this.audio.init({
track: msg.id, track: msg.id,
info: info, info: info,
raw: track.raw, raw: track.raw,
}) })
} else if (info.videoTracks) { } else if (info.videoTracks.length) {
this.video.init({ this.video.init({
track: msg.id, track: msg.id,
info: info, info: info,
@ -160,13 +162,13 @@ export class Player {
// Wait until we learn if this is an audio or video track // Wait until we learn if this is an audio or video track
const info = await track.info const info = await track.info
if (info.audioTracks) { if (info.audioTracks.length) {
this.audio.segment({ this.audio.segment({
track: msg.init, track: msg.init,
buffer: stream.buffer, buffer: stream.buffer,
reader: stream.reader, reader: stream.reader,
}) })
} else if (info.videoTracks) { } else if (info.videoTracks.length) {
this.video.segment({ this.video.segment({
track: msg.init, track: msg.init,
buffer: stream.buffer, buffer: stream.buffer,

View File

@ -743,6 +743,11 @@
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
"@types/audioworklet@^0.0.41":
version "0.0.41"
resolved "https://registry.yarnpkg.com/@types/audioworklet/-/audioworklet-0.0.41.tgz#ed2e67c7dcbe207974c54812e21819984c74a4f0"
integrity sha512-8BWffzGoSRz436IviQVPye75YYWfac4OKdcLgkZxb3APZxSmAOp2SMtsH1yuM1x57/z/J7bsm05Yq98Hzk1t/w==
"@types/dom-webcodecs@^0.1.6": "@types/dom-webcodecs@^0.1.6":
version "0.1.6" version "0.1.6"
resolved "https://registry.yarnpkg.com/@types/dom-webcodecs/-/dom-webcodecs-0.1.6.tgz#204f2c8c2e6286114e1ec2997630bc0fd2081ffa" resolved "https://registry.yarnpkg.com/@types/dom-webcodecs/-/dom-webcodecs-0.1.6.tgz#204f2c8c2e6286114e1ec2997630bc0fd2081ffa"