temp

AuthorKonata <konata@posteo.jp>
Date
Commita895aa32f7c12a4052f3e5917b29c4191a01a009
Parentb76351b
3 files changed, 78 insertions(+), 1 deletion(-)
▾Mwebclient/src/App.tsx
@@ -72,6 +72,7 @@ const App: Component = () => {
controlsDisplay: FileDisplay.TITLE,
progressBarHeight: 10,
useMSE: "MediaSource" in window,
rememberDir: false,
duplicateWarning: true,
});
@@ -187,19 +188,84 @@ const App: Component = () => {
}
}
let mediaSource: MediaSource | null = null;
function getMSEUrl(srcUrl: string, mimeType: string) {
if (!MediaSource.isTypeSupported(mimeType)) return null;
console.log("Using MSE for", srcUrl, mimeType);
mediaSource = new MediaSource();
const thisMediaSource = mediaSource;
mediaSource.addEventListener("sourceopen", async () => {
if (!thisMediaSource) return;
thisMediaSource.duration = 100;
const sourceBuffer = thisMediaSource.addSourceBuffer(mimeType);
sourceBuffer.timestampOffset = 10;
try {
const response = await fetch(srcUrl, { credentials: "same-origin" });
if (!response.ok || !response.body) throw new Error("Failed to fetch stream");
const reader = response.body.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const push = () => {
// If buffer updating, wait
if (sourceBuffer.updating) {
sourceBuffer.addEventListener("updateend", push, { once: true });
return;
}
if (thisMediaSource?.readyState !== "open") return;
sourceBuffer.appendBuffer(value.slice(0).buffer);
};
push();
}
if (sourceBuffer.updating) {
sourceBuffer.addEventListener(
"updateend",
() => {
try {
thisMediaSource.endOfStream();
} catch {}
},
{ once: true },
);
} else {
try {
thisMediaSource.endOfStream();
} catch {}
}
} catch (error) {
console.error(error);
try {
thisMediaSource.endOfStream();
} catch {}
}
});
return URL.createObjectURL(thisMediaSource);
}
// Play a track from the playlist
async function playTrack(track: PlaylistItem) {
try {
const src = await getSrc(track.path, options, isOffline());
if (!src) return;
//Cleanup
if (audioElement.src.startsWith("blob:")) URL.revokeObjectURL(audioElement.src);
if (mediaSource) {
try {
mediaSource.endOfStream();
} catch {}
}
//need both of these lines to work without logging an error
//just removeAttribute doesn't work at all
//just srcObject = null works, but logs a weird csp error
audioElement.removeAttribute("src");
audioElement.srcObject = null;
audioElement.src = src;
//TODO: get correct mimetype
const mseURL = options.useMSE && !src.startsWith("blob:") ? getMSEUrl(src, 'audio/webm; codecs="opus"') : null;
audioElement.src = mseURL || src;
batch(() => {
setPlayerState("isPlaying", true);
setPlayerState("currentTime", 0);
@@ -288,6 +354,7 @@ const App: Component = () => {
// Update progress when the time updates
function updateProgress() {
console.log(audioElement.duration);
setPlayerState("currentTime", audioElement.currentTime);
setPlayerState("buffered", reconcile(timeRangesToArray(audioElement.buffered)));
}
▾Mwebclient/src/components/OptionsModal.tsx
@@ -263,6 +263,15 @@ export default function OptionsModal(props: OptionsModalProps) {
</div>
</Match>
<Match when={optionsPage() === 3}>
<div>
<label for="mse-checkbox">Allow Media Source Extensions (MSE):</label>
<input
id="mse-checkbox"
type="checkbox"
checked={newOptions.useMSE}
onChange={(e) => setNewOptions("useMSE", e.target.checked)}
/>
</div>
<div>
<label for="remember-dir-checkbox">Remember last directory:</label>
<input
▾Mwebclient/src/types.ts
@@ -44,6 +44,7 @@ export interface AppOptions {
fontResolution: number;
progressBarHeight: number;
useMSE: boolean;
rememberDir: boolean;
duplicateWarning: boolean;
}