I have a React web app that gets the video URL from a server, then requests the video as a blob and tries to play this on an HTML video tag. I'm doing this because the client sometimes has issues with the internet and videos can stutter while playing, they'd rather wait longer for the video to load and then play it smoothly than have a choppy video. (I'm also saving the blob to IndexedDB as cache, but that's not related to the issue I'm having now, I'm just adding this as context but it has been disabled while I try to figure out this iOS problem)
I have a function to download the video, which then returns the Blob and a URL created from that blob object.
async function downloadVideo(videoUrl) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', videoUrl, true);
req.responseType = 'blob';
req.onload = function() {
// Onload is triggered even on 404
// so we need to check the status code
if (this.status === 200) {
var videoBlob = new Blob([this.response], { type: 'video/mp4' });
console.log('Video blob?', videoBlob);
var vid = { objBlob: videoBlob, vidURL: URL.createObjectURL(videoBlob) };
// Video is now downloaded and converted into ObjectURL
resolve(vid);
} else {
reject('Video download failed with status ', this.status);
}
};
req.onerror = function() {
reject('Unable to Download Video');
};
req.send();
});
}
And then I have the element that plays the blob video:
<video
muted={true}
autoPlay={true}
loop={false}
onError={err => {
alert('Video load error. ' + err.target.error.iosMessage);
}}
src={downloadedVideo.url}
/>
That downloadedVideo.url is the blob object URL created on the DownloadVideo function
All of this works fine on desktop (Linux) and on Android, but the video doesn't play from the Blob on iOS devices. I've tried Safari, Chrome, and Firefox and the problem is the same.
on iOS I can get the video Blob and create an URL from it, but when I pass it as src it doesn't work, all I can get from the error (a MediaError object) is the code, 4, but the message is undefined.
If instead of the blob I pass the original video URL as src, it works on all devices, but then I can't cache the video and this feature will have to be dropped.
I've tried several videos and made sure encoding was compatible with iOS.
I could not find anything stating that iOS is not compatible with Blob URLs for video, so this should work, but I can't figure out why it doesn't.
Save the captured video with the type of "mp4"
IMPORTANT >>> new Blob(vid, {type: "video/mp4", })
const blobb = await new Blob(vid, {type: "video/mp4", }); // Important Line
I have a similar problem, but only on iOS 15.x (it works fine till iOS 14.5)
I think it's a bug on iOS 15.x - see also https://developer.apple.com/forums/thread/693447
There is a bug in webkit on iOS 15 builds, that do not include byte range headers to blob urls.
See: https://bugs.webkit.org/show_bug.cgi?id=232076 and Safari on iOS 15 inline video playback issues
As noted in the webkit issue, there is a workaround using a service worker.
https://bug-232076-attachments.webkit.org/attachment.cgi?id=442151
Mind though, service workers do not work in WKWebView.
Related
I have a mobile website that takes a few pictures (environment cam) and records a short video (user cam). On desktop, everything works fine. On mobile, the camera feed is shown on both chrome and Safari. Taking pictures also works, but when I try to start recording, the page does not execute any javascript code after mediarecorder.start(1000). This means instructions are not shown and the vid never stops recording.
Code:
async function start()
{
var constraints = { video: { width: { ideal: 4096 }, height: { ideal: 2160 }, facingMode: 'user'}};
cameraStream = await navigator.mediaDevices.getUserMedia(constraints);
video.srcObject = cameraStream; video.play();
mediaRecorder = new MediaRecorder(cameraStream,{ mimeType: 'video/webm' });
mediaRecorder.addEventListener('dataavailable', function(e) {
chunks.push(e.data);
});
}
function startRecording()
{
console.log("starting recording")
takePicture();
console.log("Selfie taken")
outline.style.display = 'none';
button.style.display = 'none';
text.innerText = "Volg de instructies op het scherm.";
//WORKS FINE TILL HERE
mediaRecorder.start(1000);
//BELOW THIS IS NEVER EXECUTED
console.log("setting timeout");
setTimeout(step,2000);
}
As said, it works on desktop, but not on iOS chrome or Safari.
Chrome and Safari on iOS are unfortunately more or less the same. Apple only allows their own browser engine on iOS and Chrome plays by those rules.
The MediaRecorder in Safari doesn't support 'video/webm' which is why I guess there is already an error thrown when you construct the MediaRecorder. Consequently the mediaRecorder variable is undefined when you try to call start() later on.
It probably works if you let Safari (or Chrome on iOS) pick the mimeType itself by omitting the configuration.
mediaRecorder = new MediaRecorder(cameraStream);
I have implemented the WebRTC in my angular project to record the video. And after the save we can send it to the attachment. This is working fine in windows OS properly, but in mac safari, the video is speed up and 30-sec video becomes 3 sec only. this occurs only in safari.
Here on start the video.
mediaDevices.getUserMedia({ video: true, audio: true })
.then(webcamStream => {
this.webcamStream = webcamStream;
})
The MediaRecorder code:
this.recorder = new MediaRecorder(this.webcamStream, {mimeType: 'video/mp4'});
this.recorder.onstart = () =>
this.zone.run(() => {
this.behaviorService.isRecording(true);
});
this.recorder.onstop = this.onRecorderStopped;
this.recorder.ondataavailable = (event) =>
this.zone.run(() => {
this.data = [...this.data, event.data];
});
this.recorder.start();
When video is stopped then it save in video/webm;codecs=h264 this mimeType.
I have also tried with video/mp4 but it also not working
Can I get any solution that works in both OS?
Safari is notoriously broken with respect to .getUserMedia() and the MediaRecorder class.
Can i get the any solution which works in both OS?
Not yet. Pester Apple. In the meantime use Chrome on MacOS: it works.
There may be some tricks to recommend to make this better. But you didn't show us your MediaRecorder code: that's where the stream is compressed.
I'm trying to stream a video directly to a browser using node.js as the backend. I would like the video to be streamed from a specific time and would also like it to be partially streamed since it is a pretty large file. Right now I am doing this with fluent-ffmpeg, like this:
const ffmpeg = require('fluent-ffmpeg');
app.get('/clock/', (req, res) => {
const videoPath = 'video.mp4';
const now = new Date();
ffmpeg(videoPath)
.videoCodec('libx264')
.withAudioCodec('aac')
.setStartTime(`${(now.getHours() - 10) % 24}:${now.getMinutes() - 1}:${now.getSeconds()}`)
.format('mp4')
.outputOptions(['-frag_duration 100','-movflags frag_keyframe+faststart','-pix_fmt yuv420p'])
.on('end', () => {
console.log("File has been converted succesfully");
})
.on('error', (err) => {
if (err.message.toLowerCase().includes('output stream closed')) return;
console.log('An error occoured', err);
})
.pipe(res, { end: true });
});
This will work with Chrome, but Safari just doesn't want to stream it.
I know that the reason why it doesn't work on Safari is that Safari needs the range header. I've therefore tried to do that, but:
I can't get it to work with fluent-ffmpeg.
When I try to do it the "normal" way, without fluent-ffmpeg, it needs to load the whole video file before it plays.
The video doesn't need to start at the specific timestamp. It would be nice tho, but I have a workaround for that if it's not possible :)
So my question is: How can I get the code above to work with Safari. And if that is impossible: How can I code something that doesn't need to be loaded fully, before it can be played in Safari browsers, aka. partial video streaming.
I've used RecordRTC in order to record audio and send it to a speech-to-text API.
Somehow, it all works perfectly fine except for using Safari IOS.
While using Safari IOS, the recording which I'm retrieving as base64 string,
is somehow returned empty from the recorder object.
Previous questions asked about it were answered to use another library,
yet the docs for RecordRTC specifically says it fully supports Safari IOS.
Could you please help me figuring out the problem and finding a workaround?
My code:
async initMic() {
let stream = await navigator.mediaDevices.getUserMedia({video: false, audio: true});
mic = new RecordRTCPromisesHandler(stream, {
type: 'audio',
mimeType: 'audio/wav',
recorderType: RecordRTC.StereoAudioRecorder,
sampleRate: 48000,
numberOfAudioChannels: 1,
});
},
async sendRecording() {
let vm = this;
mic.stopRecording(function() {
mic.getDataURL(function(dataURL) {
vm.$store.dispatch('UpdateAudioBase64', dataURL.replace('data:audio/wav;base64,', ''));
mic.reset();
vm.$emit('send-recording');
});
});
},
** The string 'replace' function is meant to remove the base64 header
before sending it to speech-to-text API (API's needs).
Thank You!
If not mistaken, apple fu... messed up again with their dumb policy,
problem is you can't do a lot of things(like setting up recorder)
without USER trigger them,
so you should wrap your recorder in click event listener,
user click button, then your mic = new RecordRTCPromisesHandler(stream, {... etc
fires and recording starts.
check this example https://github.com/muaz-khan/RecordRTC/blob/master/simple-demos/audio-recording.html
here this trick works
btw your code works in mac safari?
I am developing this JavaScript game, that manages it's loading of assets, prior anything happens. At first, the preloader class loads up an assets json file, that contains all listed assets to load. This file contains links to aprox. 30 images, 8 json files and 5 videos. Every single asset apart from the video files load fine in every major browser (firefox, safari, explorer, chrome), but the loading of those 5 video files are aborted by internet explorer.
I'm using HTML boilerplate, maybe that has something to do with it.
This is what I get, in network view:
Link to full size image file
Sometimes, the first two videos load, sometimes none of them do. Here's an chunk of my code to load those videos:
case "ogv":
case "mp4":
item = document.createElement('video');
var itemSource = document.createElement('source');
item.id = 'vid'; item.width = '600'; item.height = '450'; item.controls = false;
if (extension == "mp4") {
itemSource.id = 'mp4'; itemSource.type = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"';
} else if (extension == "ogv") {
itemSource.id = 'ogv'; itemSource.type = 'video/ogg; codecs="theora, vorbis"';
}
itemSource.src = pathToAsset;
item.appendChild(itemSource);
item.addEventListener("canplaythrough", onVideoCanPlaythrough, false);
item.load();
break;
And here's the listener handler:
var onVideoCanPlaythrough = function () {
console.log("onVideoCanPlaythrough");
item.removeEventListener("canplaythrough", onVideoCanPlaythrough, false);
assets[assetID] = item;
callback.call();
}
The console.log("onVideoPlaythrough) does not fire a single time on Internet Explorer.
I have checked to make sure, that every other thing is correct (I get right url's in my pathToAsset variable and so on. This works on every other browser (except opera, which I haven't really tested yet), but not on IE
Can anyone suggest how to approach debugging of this sort of problem?
Apparently you initiate loading of the videos with video.play() statement, not video.load() on internet explorer 9. That's just great!