As the title says I have some video saved on an s3 bucket. I set my nodejs to stream it on my react app. It works fine on all devices except iOS. I did some searches and I can't find the issue. The server is returning the initial bytes request as 206. I checked the headers but I can't find the issue:
Here is my nodejs:
After it reaches the path:
if (!range) {
res.status(400).send("returning err!");
return;
}
s3.headObject({
Bucket: process.env.AWS_BUCKET_NAME,
Key: req.params.key
}, function (err, data) {
if (err) {
// an error occurred
console.error(err);
return res.status(500).send("returning err");
}
const videoSize = Number(data.ContentLength);
const CHUNK_SIZE = 10 ** 6; // 1MB
const start = Number(range.replace(/\D/g, ""));
const end = Math.min(start + CHUNK_SIZE, videoSize - 1);
var params = {
Bucket: process.env.AWS_BUCKET_NAME,
Key: req.params.key,
Range: range,
};
var stream = s3.getObject(params).createReadStream();
res.status(206);
res.set('Content-Type', data.ContentType);
res.set('Content-Disposition','inline');
res.set('Accept-Ranges','bytes');
res.set('Accept-Encoding', 'Identity');
res.set('Content-Range', 'bytes ' + start + '-' + end + '/' + videoSize);
res.set('Content-Length', data.ContentLength);
res.set('X-Playback-Session-Id', req.header('X-Playback-Session-Id')); // Part of the attempt to fix
res.set('Connection', 'keep-alive');
res.set('Last-Modified', data.LastModified);
res.set('ETag', data.ETag);
stream.pipe(res);
Here is my Frontend React player code:
<ReactPlayer
ref={player}
// onProgress={onProgress}
playsinline={true}
url={[{ src: source, type: 'video/mp4;' }]} // video location
controls // gives the front end video controls
width='100%'
className='react-player'
height='100%'
allow='autoplay; encrypted-media'
allowFullScreen
// muted={true}
playing={playing}
onPlay={() => setPlaying(true)}
// onPause={() => setPlaying(false)} //part of the attempt to fix
// onSeek={(seek) => playerSeeker(seek)} //part of the attempt to fix
config={{
file: {
attributes: {
controlsList: 'nodownload'
}
}
}}
onContextMenu={e => e.preventDefault()}
onEnded={(e) => onVideoEnded(e)}
onError={(e) => onVideoError(e)}
/>
Again, the first request on iOS is returning a 206 success but node always ends the stream before it even start playing.
Turns out It was just the
res.set('Content-Length', data.ContentLength);
Instead of sending the full length for the video, I needed to return the length of the calculated range.
Related
I tried to set GaussianBlurBackgroundProcessor (I use this post as a starting point but instead of node.js i use *.min.js in a php page). On local video it works but when I connect my video in a room, remote partecipants see my video "clean".
Someone had have my same problem?
I use min version of:
twilio-video.js 2.22.1
twilio-video-processors.js 1.0.2
This is the code:
[...]
const TWVideo = Twilio.Video;
const bg = new Twilio.VideoProcessors.GaussianBlurBackgroundProcessor({
assetsPath: '',
maskBlurRadius: 5,
blurFilterRadius: 25,
});
bg.loadModel();
const localVideo = TWVideo.createLocalVideoTrack().then(track => {
let video = document.getElementById('local-media').firstElementChild;
setProcessor(bg, track);
video.appendChild(track.attach());
$('#local-media').find('video').css('width', '200px');
});
TWVideo.connect(room_token, {
name: roomName
}).then(room => {
window.room = activeRoom = room;
log('Connected to Room '+ roomName);
room.participants.forEach(participantConnected);
room.on('participantConnected', participantConnected);
room.on('participantDisconnected', participantDisconnected);
room.once('disconnected', error => room.participants.forEach(participantDisconnected));
room.on('reconnecting', error => {
assert.equal(room.state, 'reconnecting');
if (error.code === 53001) {
console.log('Reconnecting your signaling connection!', error.message);
}
else if (error.code === 53405) {
console.log('Reconnecting your media connection!', error.message);
}
});
room.on('reconnected', () => {
console.log('Reconnected your signaling and media connections!');
assert.equal(room.state, 'connected');
});
room.on('participantReconnected', remoteParticipant => {
console.log("${remoteParticipant.identity} has reconnected the signaling connection to the Room!");
assert.equals(remoteParticipant.state, 'connected');
})
});
[...]
Thanks!
The issue is that you are creating a local video track and applying the blur to it, but you’re not using that track when you connect to the room. I would create the local video track and audio track first, then apply them to the room when you connect like this:
Video.connect(roomToken, {
name: roomName,
tracks: [localVideo, localAudio]
}).then(…);
Works on: Windows PC, Ubuntu PC, MacOS Chrome
Does not work on: iPhone Chrome, iPhone Safari, MacOs Safari
Video just doesn't play there are no errors. Images are working on all platforms.
I am uploading raw videos to S3 with nodejs and multer, tried to use ffmpeg to encode videos to but didnt help.
public upload(file) {
const fileStream = fs.createReadStream(file.path)
let fileKey = `${file.filename}`
const uploadParams = {
Bucket: bucketName,
Body: fileStream,
Key: fileKey,
}
return s3.upload(uploadParams).promise()
}
Nodejs:
public async download(fileKey) {
const downloadParams = {
Key: fileKey,
Bucket: bucketName,
}
try {
const object = await s3.getObject(downloadParams).promise() // This throws error if image not found and catch get it
return {readStream: s3.getObject(downloadParams).createReadStream(), object}
} catch (err) {
throw new NotFoundError()
}
}
router.get('/file', async (req, res) => {
const { key } = req.query
const { readStream, object } = await Container.get(S3Service).download(key)
if (readStream.httpCode) {
res.json({ error: 'error' })
return
}
if(key.split('.')[1] === 'mp4') {
const range = req.headers.range
const bytes = range.replace(/bytes=/, '').split('-')
const start = parseInt(bytes[0], 10)
const total = object.ContentLength
const end = bytes[1] ? parseInt(bytes[1], 10) : total - 1
const chunksize = end - start + 1
res.status(206)
res.set('Content-Type', object.ContentType)
res.set('Content-Length', chunksize)
res.set('Content-Disposition', 'inline')
res.set('Accept-Ranges', 'bytes')
res.set('Accept-Encoding', 'Identity')
res.set('Content-Range', 'bytes ' + start + '-' + end + '/' + total)
res.set('X-Playback-Session-Id', req.header('X-Playback-Session-Id'))
res.set('Connection', 'keep-alive')
res.set('Last-Modified', object.LastModified)
res.set('ETag', object.ETag)
} else {
res.type('png')
}
readStream.pipe(res)
})
Angular:
<video playsinline muted autoplay controls="true" preload="metadata">
<source [src]="message.files[0].fileUrl" type="video/mp4" />
<source [src]="message.files[0].fileUrl" type="video/avi" />
<source [src]="message.files[0].fileUrl" type="video/ogg" />
<source [src]="message.files[0].fileUrl" type="video/webm" />
</video>
dev,
Seems browser capability issue.
Try to run the website in chrome dev tools as iphone and try to do same with ie
Sometime all browser doesn’t support all feature. Majorly on mobile browser, we not have dev option see what is going wrong with dev tools
Regards,
Muhamed
As far as i know to work with videos on apple devices your server must support range request to play video.
Range requests: https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
You can google more details about safari and range requests on stack overflow or google
Safari browsers above iOS 14 cannot play m3u8 videos and cannot load .ts files
Safari browsers below ios 14 can play
First request the m3u8 file, then request the corresponding decryption key, then perform key replacement and ts resource domain replacement, and then generate base64 and put it in video src.
There is no problem with this operation below ios 14 but not above ios 14
async iosAutoKey(xhr) {
const me = this;
const res = me.addVideoFilePrefix(xhr.response);
let resKey = "";
resKey = await api
.downloadCertificateKeyH5({
videoId: me.videoData.id,
})
.then((resKey) => {
return resKey;
});
let key = new Blob([resKey], {
type: "text/plain",
});
const keyUrl = URL.createObjectURL(key);
let blob = new Blob(
[res.replace(/URI="[\d]{13}"|URI="{REMOTE_KEY}"/, `URI="${keyUrl}"`)],
{
type: "application/vnd.apple.mpegurl",
}
);
let reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
const url = reader.result;
me.arrayBufferMap.set(me.videoData.id, url);
if (Hls.isSupported()) {
me.hlsInstance.loadSource(url);
} else if (isIOS()) {
me.theVideo.src = url;
} else {
me.$dialog.alert({
message: "The current browser does not support playing m3u8, please use the latest version of chrome",
});
}
};
},
addVideoFilePrefix(res) {
let result;
let reg = new RegExp('(http|https)://.+/group');
let prefix = `${this.vipFileSource}/group`;
if (reg.test(res)) {
result = res.replace(/(http|https):\/\/.+\/group/g, prefix);
} else {
result = res.replace(/\/group/g, prefix);
}
return result;
},
Video trigger event below ios 14
enter image description here
Video trigger event above ios 14
enter image description here
m3u8 first load
enter image description here
The m3u8 file decrypted on ios 14 cannot load the video, and the .ts file cannot be loaded
m3u8 decrypt the requested base64
enter image description here
I'd like to upload a video and get direct URL to it (not a YouTube page, just a raw video file on a server). I've read here that youtube-dl can get such a direct link from YT video, but it returned something like this:
https://r5---sn-f5f7ln7s.googlevideo.com/videoplayback?id=5e338da1be872622&itag=
140&source=youtube&requiressl=yes&mm=31&pl=17&mn=sn-f5f7ln7s&mv=m&ms=au&ratebypa
ss=yes&mime=audio/mp4&gir=yes&clen=615762&lmt=1444814917264017&dur=38.730&key=dg
_yt0&signature=85FE55338A7ECBCA4895DFA3084A6C8CB7C09654.28AD612266C937BFBBD20135
D03031E824806B53&sver=3&mt=1444818958&fexp=9405191,9408210,9408710,9414764,94154
35,9415868,9416126,9417707,9418199,9418401,9418702,9420439,9420933,9421923,94220
62,9422545&upn=5cQZ5KNCvl0&ip=89.74.115.72&ipbits=0&expire=1444840721&sparams=ip
,ipbits,expire,id,itag,source,requiressl,mm,pl,mn,mv,ms,ratebypass,mime,gir,clen
,lmt,dur
The thing is, it plays but shows nothing (indeed, it opens as a video file). So it looks like YT is somehow protected from such actions.
Do you know any site that allows me to do such thing?
Thanks!
youtube-dl works to download videos from a wide variety of sites, not just YT
youtube-dl -c -l https://www.youtube.com/watch?v=fT88iVceBn4
Once you have a media file (audio/video) on your server (or your local machine), the following nodejs server will stream it to a URL ...
save below node code into file media_server.js then once you have nodejs installed launch the below code doing
node ./media_server.js
then point your browser at http://localhost:8888/
its a tiny media server which responds to all the standard media navigation widgets ... have fun
var http = require('http'),
fs = require('fs'),
util = require('util');
// var path = "/path/to/audio/or/video/file/local/to/server/cool.mp4"; // put any audio or video file here
var path = "/home/stens/Videos/kehoe/OliverSacks_2009-480p.mp4"; // put any audio or video file here
var port = 8888;
var host = "localhost";
http.createServer(function (req, res) {
var stat = fs.statSync(path);
var total = stat.size;
if (req.headers.range) { // meaning client (browser) has moved the forward/back slider
// which has sent this request back to this server logic ... cool
var range = req.headers.range;
var parts = range.replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];
var start = parseInt(partialstart, 10);
var end = partialend ? parseInt(partialend, 10) : total-1;
var chunksize = (end-start)+1;
console.log('RANGE: ' + start + ' - ' + end + ' = ' + chunksize);
var file = fs.createReadStream(path, {start: start, end: end});
res.writeHead(206, { 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4' });
file.pipe(res);
} else {
console.log('ALL: ' + total);
res.writeHead(200, { 'Content-Length': total, 'Content-Type': 'video/mp4' });
fs.createReadStream(path).pipe(res);
}
}).listen(port, host);
console.log("Server running at http://" + host + ":" + port + "/");
Is there support to Capture Video with Sound in phonegap (cordova)?
I checked on my iPad a phonegap 3.1 app whit Video Capture and no sound recording with the video
appreciate help
Thanks
function uploadFile(mediaFile) {
var ft = new FileTransfer(),
path = mediaFile.fullPath,
name = mediaFile.name;
ft.upload(path,
"http://www/api/up_video",
function(result) {
cordova.logger.log('Upload success: ' + result.responseCode);
cordova.logger.log(result.bytesSent + ' bytes sent');
},
function(error) {
cordova.logger.log('Error uploading file ' + path + ': ' + error.code);
},
{ fileName: name });
}
// capture callback
var captureSuccess = function(mediaFiles) {
var i, path, len;
for (i = 0, len = mediaFiles.length; i < len; i += 1) {
path = mediaFiles[i].fullPath;
name = mediaFiles[i].name;
// do something interesting with the file
alert(name);
//alert(mediaFiles[i].size);
//alert(mediaFiles[i].type);
uploadFile(mediaFiles[i]);
}
};
// capture error callback
var captureError = function(error) {
navigator.notification.alert('Error code: ' + error.code, null, 'Capture Error');
cordova.logger.log( error);
};
navigator.device.capture.captureVideo(captureSuccess, captureError, {duration:6});
Absolutely. Both video and sound can be captured using Phonegap's "Capture" API. See documentation here.
Edit based on comments:
You are never telling the device to capture audio. The capture of Video and Audio are different operations. You need to also call the navigator.device.capture.captureAudio function. See here.