-
Notifications
You must be signed in to change notification settings - Fork 987
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
App: Remember login & Buffering performance #752
Comments
The profiles are added now, it was in the pipeline for release the entire time. The part about the login credentials is annoying me as well, I will have to take a look at it. Streaming performance on mobile is pretty bad, might have to do with inoic1 .upgrading to ionic4 might fix it. I will take a look at it some time. (PS: I will rename your issue for easier identification) |
This is for the website and html5 player also. buffering is easy.... just java to start downloading and feed via buffer stream to the player.... have setting in settings for length of buffer and you are golden. As for the streams to the app, that can be handled by buffering via java script. I was already poking around.... https://stackoverflow.com/questions/35239044/mpeg-dash-video-stream-with-just-single-mp4-file I remember seeing how to use java to do buffering, I just can't remember where. must read Thanks @dularion ! |
taken from link to source code for player, this is what I think you could add and would solve most issues. Thanks, |
@mcrook250 we do handle the buffering already via Accept-Ranges header. If thats what you mean. see the code here https://github.com/streamaserver/streama/blob/master/grails-app/services/streama/FileService.groovy#L14 however, this seems broken on android, as it loads all the content at once instead of chunking it. If you are referring to something else, let me know. |
No, this is to do with when you watch a movie and you are relying on the browser to buffer enough and sometimes you can not. workflow:
const NUM_CHUNKS = 5;
var video = document.querySelector('video');
video.src = video.webkitMediaSourceURL;
video.addEventListener('webkitsourceopen', function(e) {
var chunkSize = Math.ceil(file.size / NUM_CHUNKS);
// Slice the video into NUM_CHUNKS and append each to the media element.
for (var i = 0; i < NUM_CHUNKS; ++i) {
var startByte = chunkSize * i;
// file is a video file.
var chunk = file.slice(startByte, startByte + chunkSize);
var reader = new FileReader();
reader.onload = (function(idx) {
return function(e) {
video.webkitSourceAppend(new Uint8Array(e.target.result));
logger.log('appending chunk:' + idx);
if (idx == NUM_CHUNKS - 1) {
video.webkitSourceEndOfStream(HTMLMediaElement.EOS_NO_ERROR);
}
};
})(i);
reader.readAsArrayBuffer(chunk);
}
}, false); |
this might fix the android problem though... add this and some code to figure out the mime type of the file. which explains why videos aren't working on iOS or the applewebkit. response.addHeader("Etag", file.sha256Hex) |
Cool, Ill give it a try! |
About the buffering: I'll have to take a closer look at it. |
Here is something to get you started on the buffering, im not really sure if its what is needed or maybe you will need a java class like Mp4box.js <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<video controls></video>
<script>
var video = document.querySelector('video');
var assetURL = 'sample.mp4';
// Need to be specific for Blink regarding codecs
// ./mp4info frag_bunny.mp4 | grep Codec
var mimeCodec = 'video/mp4; codecs="avc1.640016, mp4a.40.2"';
var totalSegments = 5;
var segmentLength = 0;
var segmentDuration = 0;
var bytesFetched = 0;
var requestedSegments = [];
for (var i = 0; i < totalSegments; ++i) requestedSegments[i] = false;
var mediaSource = null;
if ('MediaSource' in window && MediaSource.isTypeSupported(mimeCodec)) {
mediaSource = new MediaSource;
//console.log(mediaSource.readyState); // closed
video.src = URL.createObjectURL(mediaSource);
mediaSource.addEventListener('sourceopen', sourceOpen);
} else {
console.error('Unsupported MIME type or codec: ', mimeCodec);
}
var sourceBuffer = null;
function sourceOpen (_) {
sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
getFileLength(assetURL, function (fileLength) {
console.log((fileLength / 1024 / 1024).toFixed(2), 'MB');
//totalLength = fileLength;
segmentLength = Math.round(fileLength / totalSegments);
//console.log(totalLength, segmentLength);
fetchRange(assetURL, 0, segmentLength, appendSegment);
requestedSegments[0] = true;
video.addEventListener('timeupdate', checkBuffer);
video.addEventListener('canplay', function () {
segmentDuration = video.duration / totalSegments;
video.play();
});
video.addEventListener('seeking', seek);
});
};
function getFileLength (url, cb) {
var xhr = new XMLHttpRequest;
xhr.open('head', url);
xhr.onload = function () {
cb(xhr.getResponseHeader('content-length'));
};
xhr.send();
};
function fetchRange (url, start, end, cb) {
var xhr = new XMLHttpRequest;
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.setRequestHeader('Range', 'bytes=' + start + '-' + end);
xhr.onload = function () {
console.log('fetched bytes: ', start, end);
bytesFetched += end - start + 1;
cb(xhr.response);
};
xhr.send();
};
function appendSegment (chunk) {
sourceBuffer.appendBuffer(chunk);
};
function checkBuffer (_) {
var currentSegment = getCurrentSegment();
if (currentSegment === totalSegments && haveAllSegments()) {
console.log('last segment', mediaSource.readyState);
mediaSource.endOfStream();
video.removeEventListener('timeupdate', checkBuffer);
} else if (shouldFetchNextSegment(currentSegment)) {
requestedSegments[currentSegment] = true;
console.log('time to fetch next chunk', video.currentTime);
fetchRange(assetURL, bytesFetched, bytesFetched + segmentLength, appendSegment);
}
//console.log(video.currentTime, currentSegment, segmentDuration);
};
function seek (e) {
console.log(e);
if (mediaSource.readyState === 'open') {
sourceBuffer.abort();
console.log(mediaSource.readyState);
} else {
console.log('seek but not open?');
console.log(mediaSource.readyState);
}
};
function getCurrentSegment () {
return ((video.currentTime / segmentDuration) | 0) + 1;
};
function haveAllSegments () {
return requestedSegments.every(function (val) { return !!val; });
};
function shouldFetchNextSegment (currentSegment) {
return video.currentTime > segmentDuration * currentSegment * 0.8 &&
!requestedSegments[currentSegment];
};
</script>
</body>
</html> |
tested with response.addHeader("Content-Type", "Video/MP4") and it works! I also noticed in the <video tag you don't have onPreload="auto" ? Sorry, I have a great connection back home, not so great where I am and I am having really bad buffering issues. |
@mcrook250 thanks for investigating this. I will add the content-type asap to try it out :) I will use username: good point, we use springsecurity standard stuff, but maybe we can configure that somehow. the videotag preloading: didnt know that was a thing, ill check it out :) |
your issue has now become 5 issues btw :D |
haha yeah sorry, hopefully I can help a bit by doing some research for you. A simple check box to remember usernames and password should be as simple as setting a setting via the auth cookie sent to the browser or just setting a simple cookie. You can encrypt the password just for safety. the content-type and preloading should help a lot, but I came cross this today that might help with buffering ad serving files. Matt PS I think I found the code you are looking for.... import grails.compiler.GrailsTypeChecked
import grails.plugin.springsecurity.annotation.Secured
import asset.pipeline.grails.AssetResourceLocator
import grails.util.BuildSettings
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.springframework.core.io.Resource
class VideoController {
GrailsApplication grailsApplication
AssetResourceLocator assetResourceLocator
public index() {
Resource mp4Resource = assetResourceLocator.findAssetForURI('/../lol.mp4')
String range = request.getHeader('range')
if(range) {
String[] rangeKeyValue = range.split('=')
String[] rangeEnds = rangeKeyValue[1].split('-')
if(rangeEnds.length > 1) {
int startByte = Integer.parseInt(rangeEnds[0])
int endByte = Integer.parseInt(rangeEnds[1])
int contentLength = (endByte - startByte) + 1
byte[] inputBytes = new byte[contentLength]
def inputStream = mp4Resource.inputStream
inputStream.skip(startByte) // input stream always starts at the first byte, so skip bytes until you get to the start of the requested range
inputStream.read(inputBytes, 0, contentLength) // read from the first non-skipped byte
response.reset() // Clears any data that exists in the buffer as well as the status code and headers
response.status = 206
response.addHeader("Content-Type", "video/mp4")
response.addHeader( 'Accept-Ranges', 'bytes')
response.addHeader('Content-Range', "bytes ${startByte}-${endByte}/${mp4Resource.contentLength()}")
response.addHeader( 'Content-Length', "${contentLength}")
response.outputStream << inputBytes
}
}
}
} taken from http://qaru.site/questions/6858371/streaming-mp4-requests-via-http-with-range-header-in-grails to figure out the mime type import com.google.common.net.MediaType;
import java.io.*;
import java.time.Instant;
public class FileSystemPointer implements FilePointer {
private final MediaType mediaTypeOrNull;
public FileSystemPointer(File target) {
final String contentType = java.nio.file.Files.probeContentType(target.toPath());
this.mediaTypeOrNull = contentType != null ?
MediaType.parse(contentType) :
null;
} and private ResponseEntity<Resource> response(FilePointer filePointer, HttpStatus status, Resource body) {
final ResponseEntity.BodyBuilder responseBuilder = ResponseEntity
.status(status)
.eTag(filePointer.getEtag())
.contentLength(filePointer.getSize())
.lastModified(filePointer.getLastModified().toEpochMilli());
filePointer
.getMediaType()
.map(this::toMediaType)
.ifPresent(responseBuilder::contentType);
return responseBuilder.body(body);
}
private MediaType toMediaType(com.google.common.net.MediaType input) {
return input.charset()
.transform(c -> new MediaType(input.type(), input.subtype(), c))
.or(new MediaType(input.type(), input.subtype()));
}
@Override
public Optional<MediaType> getMediaType() {
return Optional.ofNullable(mediaTypeOrNull);
} |
Did you have a chance to try out setting content-type? |
I tried it out with the content-type (hardcoded to video/mp4 as a test) but it did not improve the initial buffering time. something is still making it buffer forever and ever, whereas in the web browser there is hardly any buffering time and the video starts almost right away :/ |
you should still have that set regardless of how the android handles it. This thread was always server related and nothing really to do with android. |
I don't know anything really about android, just how to root and the basics lol I have also included many links and sample code to use to provide viable bit rate transcoding. :D |
Issue description
Feature request
Steps to Reproduce
Expected Behaviour
not having to enter login info all the time
and smooth streaming without so much buffering, like netflix
Actual Behaviour
always having to enter login info and always buffering
Environment Information
The text was updated successfully, but these errors were encountered: