forked from yume-chan/ms2109-player
-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.js
138 lines (118 loc) · 4.79 KB
/
main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
const MODE_LIST = [
// todo: re-evaluate mode list
// 720p output isn't as clear as 1080p output, but the difference is small enough
// to make prioritizing smoothness the more logical choice
{width: 1280, height: 720, frameRate: 60},
// Fallbacks
{width: 1920, height: 1080, frameRate: 30},
// MS2109 may output 25FPS when connected to a USB hub
{width: 1920, height: 1080, frameRate: 25},
{width: 1280, height: 720, frameRate: 30}
];
const AUDIO_META = {vid: '534d', pid: '2109', name: 'USB Digital Audio'};
const VIDEO_META = {vid: '534d', pid: '2109', name: 'USB Video'};
const videoElement = document.body.querySelector("video");
start().then();
async function requestMediaDevicePermission() {
// request any media device to trigger the permission popup
const stream = await window.navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
// stop all tracks so they can be requested again
for (const track of stream.getTracks()) {
track.stop();
}
}
function findDevice(devices, type, meta) {
// Spec doesn't define how to find a device with specified VID/PID
// Chrome appends (vid:pid) to the device label
let device = devices.find(
x =>
x.kind === type &&
x.label.endsWith(`(${meta.vid.toLowerCase()}:${meta.pid.toLowerCase()})`));
// look for friendly name if VID/PID not found
if (device == null) {
device = devices.find(
x =>
x.kind === type &&
x.label.includes(meta.name));
}
return device;
}
async function start() {
// Only `getUserMedia` triggers the permission popup, `enumerateDevices` won't
await requestMediaDevicePermission();
// TODO: handle permission rejected
const devices = await window.navigator.mediaDevices.enumerateDevices();
const videoDevice = findDevice(devices, 'videoinput', VIDEO_META);
const audioDevice = findDevice(devices, 'audioinput', AUDIO_META);
// TODO: handle device not found
for (const mode of MODE_LIST) {
try {
const videoStream = await window.navigator.mediaDevices.getUserMedia({
video: {
deviceId: {exact: videoDevice.deviceId},
width: {exact: mode.width},
height: {exact: mode.height},
frameRate: {exact: mode.frameRate},
},
});
videoElement.srcObject = videoStream;
// TODO: Warn about Firefox/Safari incompatibility
const audioStream = await window.navigator.mediaDevices.getUserMedia({
audio: {
deviceId: {exact: audioDevice.deviceId},
sampleRate: 96_000,
sampleSize: 16,
},
});
const context = new AudioContext({sampleRate: 96_000});
const source = context.createMediaStreamSource(audioStream);
await context.audioWorklet.addModule('data:application/javascript;charset=utf8,' + encodeURIComponent(`
class SplitProcessor extends AudioWorkletProcessor {
process (inputs, outputs, parameters) {
const input = inputs[0][0];
const leftOutput = outputs[0][0];
const rightOutput = outputs[0][1];
// Separate interleaved stereo audio into left and right channels
let i = 0;
while (i < input.length) {
// Web Audio API doesn't support sample rate conversion
// So we have to duplicate the samples
leftOutput[i] = input[i + 1];
leftOutput[i + 1] = input[i + 1];
rightOutput[i] = input[i];
rightOutput[i + 1] = input[i];
i += 2;
}
return true;
}
}
registerProcessor('split-processor', SplitProcessor)
`));
const processor = new AudioWorkletNode(context, 'split-processor', {
numberOfInputs: 1,
numberOfOutputs: 1,
});
source.connect(processor);
processor.connect(context.destination);
// fullscreen mode
document.addEventListener("keydown", function (e) {
if (e.key === 'f') {
if (!document.fullscreenElement) {
videoElement.requestFullscreen();
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
}
}
}
}, false);
break;
} catch (e) {
console.error(e);
// ignore
}
}
}