В настоящее время у меня есть настройка для эмуляции поддержки Web Workers, когда некоторые функции не поддерживаются в разных браузерах, чтобы использовать одну и ту же систему обмена сообщениями во всех браузерах. В этом случае Firefox не поддерживает расширение источника мультимедиа в веб-воркере. В «нерабочем» режиме я создаю MessageChannel и обмениваюсь сообщениями между «рабочим» потоком и основным потоком, используя порты MessageChannel. Вот как мой код выглядит в целом:
// on port1 of the MessageChannel (representing the worker side)
postMessageToMain("createVideo")
const mediaSource = new MediaSource()
if (workerSupported){
const handle = mediaSource.handle
postMessageToMain("setSource", handle, [handle])
} else {
const handle = URL.createObjectURL(mediaSource)
postMessageToMain("setSource", handle)
}
// on port2 of the MessageChannel (representing the main thread)
onmessage(m){
switch (m) {
case "createVideo":{
videoElement = document.createElement('video')
videoElement.playsInline = true
break
}
case "setSource":{
if (workerSupported){
videoElement.srcObject = m.data.handle // it is of type MediaSourceHandle
} else {
videoElement.src = m.data.handle // is of type string, here the error in the title is thrown
}
}
}
}
Этот код работает в Chrome в рабочем и нерабочем режиме, а в Safari, учитывая, что MSE в Web Worker не поддерживается, работает только в нерабочем режиме. Я НЕ устанавливаю заголовок CSP. Я делаю что-то неправильно? Я нашел ту же проблему в SO, но изо всех сил пытаюсь найти реальную проблему и ее решение.
Минимальный воспроизводимый пример: https://stackblitz.com/edit/vitejs-vite-hxbktr?file=main.js
(откройте Firefox и проверьте консоль)
Я включил его, потому что это один и тот же код независимо от того, работает он в рабочем режиме или нет. Он нужен только для того, чтобы различать две стороны MessageChannel.
В Firefox (где и возникает эта ошибка) он всегда выполняется в основном потоке. createObjectURL получает экземпляр MediaSource
Тогда MessageChannel вообще не будет иметь значения. Алгоритм клонирования видит только строку, он не знает, что эта строка представляет собой URL-адрес большого двоичного объекта, и для <video>
src
не имеет значения, что она прошла через клонирование MessageChannel
. Таким образом, вы, безусловно, можете уменьшить свою проблему на videoElement.src = new MediaSource()
, очень маловероятно, что ваша установка с поддельным работником виновата.
Я тоже так думал, но я пробовал другие демо-версии mse в Firefox, и они работали нормально, поэтому я подумал, что единственная другая переменная - это действующая система сообщений. Учитывая, что код очень простой, я не могу понять истинную причину этой проблемы, это действительно похоже на ошибку в Firefox.
Может быть, тем более, что вы говорите, что это работает в других браузерах, но я еще раз искренне сомневаюсь, что MessageChannel имеет к этому какое-либо отношение, поэтому попробуйте удалить его. Чтобы найти причину проблемы, лучше всего удалить все, что не связано с ней, пока не найдете минимум, позволяющий воспроизвести проблему. Если у вас есть только одна или две переменные, которые вызывают проблему, то, как правило, очевидно, что ее вызывает; если это не так, мы здесь, чтобы помочь. Но вам нужно создать этот минимально воспроизводимый пример . Теперь не забудьте также отключить все плагины браузера, которые у вас могут быть.
Извините за задержку, после нескольких часов отладки мне удалось найти виновника в MessageChannel. Вот минимальный репродукция: stackblitz.com/edit/vitejs-vite-hxbktr?file=main.js При открытии этой демонстрации в Firefox возникает ошибка безопасности.
Очень странная ошибка, из-за которой кажется, что они не поддерживают связь между MediaSource и URL-адресом blob:, когда этот последний не используется в той же задаче, в которой он был создан. Вот реальный минимальный пример:
const video = document.querySelector('video');
const mediaSource = new MediaSource();
const url = URL.createObjectURL(mediaSource);
setTimeout(() => {
video.src = url;
});
<video controls></video>
Синхронная установка src позволит избежать ошибки.
const video = document.querySelector('video');
const mediaSource = new MediaSource();
const url = URL.createObjectURL(mediaSource);
video.src = url;
<video controls></video>
и то же самое можно сделать с созданием URL-адреса blob: из асинхронного обратного вызова:
const video = document.querySelector('video');
const mediaSource = new MediaSource();
setTimeout(() => {
video.src = URL.createObjectURL(mediaSource);
});
<video controls></video>
Итак, в вашем случае вы можете вместо этого высмеять контекст Worker с помощью синхронных операций:
// assuming you don't use addEventListener()
const fakeMain = { postMessage(data) { fakeWorker.onmessage?.({ data }); } };
const fakeWorker = { postMessage(data) { fakeMain.onmessage?.({ data }); } };
/*
Copyright 2017 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
'use strict';
// This code adapted from Eric Bidelman's demo at
// http://html5-demos.appspot.com/static/media-source.html
var FILE = 'https://upload.wikimedia.org/wikipedia/commons/a/a4/BBH_gravitational_lensing_of_gw150914.webm';
var NUM_CHUNKS = 5;
var video = document.querySelector('video');
if (!window.MediaSource) {
alert('The MediaSource API is not available on this platform');
}
const fakeMain = { postMessage(data) { fakeWorker.onmessage?.({ data }); } };
const fakeWorker = { postMessage(data) { fakeMain.onmessage?.({ data }); } };
fakeWorker.onmessage = (message) => {
console.info('received message', message);
switch (message.data.type) {
case 'init': {
console.info('received message in worker');
const mediaSource = new MediaSource();
mediaSource.addEventListener(
'sourceopen',
function () {
var sourceBuffer = mediaSource.addSourceBuffer(
'video/webm; codecs = "vorbis,vp8"'
);
console.info(sourceBuffer);
log('MediaSource readyState: ' + this.readyState);
get(FILE, function (uInt8Array) {
var file = new Blob([uInt8Array], {
type: 'video/webm',
});
var chunkSize = Math.ceil(file.size / NUM_CHUNKS);
log('Number of chunks: ' + NUM_CHUNKS);
log('Chunk size: ' + chunkSize + ', total size: ' + file.size);
// Slice the video into NUM_CHUNKS and append each to the media element.
var i = 0;
(function readChunk_(i) {
// eslint-disable-line no-shadow
var reader = new FileReader();
// Reads aren't guaranteed to finish in the same order they're started in,
// so we need to read + append the next chunk after the previous reader
// is done (onload is fired).
reader.onload = function (e) {
sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
log('Appending chunk: ' + i);
if (i === NUM_CHUNKS - 1) {
sourceBuffer.addEventListener('updateend', function () {
if (
!sourceBuffer.updating &&
mediaSource.readyState === 'open'
) {
mediaSource.endOfStream();
}
});
} else {
if (video.paused) {
video.play(); // Start playing after 1st chunk is appended.
}
readChunk_(++i);
}
};
var startByte = chunkSize * i;
var chunk = file.slice(startByte, startByte + chunkSize);
reader.readAsArrayBuffer(chunk);
})(i); // Start the recursive call by self calling.
});
},
false
);
mediaSource.addEventListener(
'sourceended',
function () {
log('MediaSource readyState: ' + this.readyState);
},
false
);
const url = window.URL.createObjectURL(mediaSource);
fakeWorker.postMessage({ type: 'setSrc', url });
}
}
};
fakeMain.onmessage = (message) => {
switch (message.data.type) {
case 'setSrc': {
console.info('received message in main');
video.src = message.data.url;
}
}
};
document.querySelector('[data-num-chunks]').textContent = NUM_CHUNKS;
fakeMain.postMessage({ type: 'init' });
function get(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'arraybuffer';
xhr.send();
xhr.onload = function () {
if (xhr.status !== 200) {
alert('Unexpected status code ' + xhr.status + ' for ' + url);
return false;
}
callback(new Uint8Array(xhr.response));
};
}
function log(message) {
document.getElementById('data').innerHTML += message + '<br /><br />';
}
<video controls></video>
<div data-num-chunks></div>
<div id = "data"></div>
Я вижу, вы открыли https://bugzil.la/1906040 Будем надеяться, что это наберет обороты и скоро исправится.
const mediaSource = new MediaSource()
в Firefox всегда выполняется в основном потоке, поскольку он не поддерживается в рабочих потоках. Вот почему я создаю эту систему обмена сообщениями в качестве запасного варианта, когда некоторые функции не поддерживаются в Web Workers.