Streaming mp4 from grails controller not working on iPhone - ios

I'm trying to stream a mp4 file to iOS devices (iPhone and iPad) from a Grails controller:
def streamContent() {
def contentPath = "/path/to/file"
File f = new File(contentPath)
if(f.exists()) {
response.setContentType("video/mp4")
response.outputStream << f.newInputStream()
response.outputStream.flush()
response.outputStream.close()
} else {
render status: 404
}
}
this code works well on desktop browsers like safari (I see the video), but when I access to the same page with an iPhone or an iPad the video won't play. Note that if I put the same video on Apache httpd and I request it from iOS devices, there is no problem. So it must be a streaming problem.
On the html page the video is embedded using HTML5 video tag:
<video width="360" height="200" controls>
<source src="http://localhost:8080/myapp/controller/streamContent" type='video/mp4'>
</video>

I solved this problem by handling Partial Content and Range Requests (HTTP 206 status). It seems that mobile browsers/media players are using partial requests the avoid to much data transfer all at once.
So instead of doing a simple
response.outputStream << f.newInputStream()
I read only the requested bytes when the request is for a range of bytes:
if (isRange) {
//start and end are requested bytes offsets
def bytes = new byte[end-start]
f.newInputStream().read(bytes, start, bytes.length)
response.outputStream << bytes
response.status = 206
} else {
response.outputStream << f.newInputStream()
response.status = 200
}

I don't have enough reputation to comment yet, but I just want to point out that the answer above is not complete, specifically that you need to include additional headers, and that the usage of f.newInputStream().read() is not being used accurately, as it won't just read a chunk at any starting point from the input stream, but it will read a chunk from the current position, so you must use save the inputStream and then use inputStream.skip() to jump to the right position.
I have a more complete answer here (where I answered my own similar question)
https://stackoverflow.com/a/23137725/2601060

Related

Streaming video with ASP.NET WebAPI - Video seeking issue

I am using PushStreamContent to stream the video hosted as a BLOB asynchronously. Here's what I've done so far
public HttpResponseMessage Get(string filename, string extension)
{
var video = new VideoStream(filename, extension);
var response = Request.CreateResponse();
response.Content =
new PushStreamContent(
(Action<Stream, HttpContent, TransportContext>) video.WriteToStream,
new MediaTypeHeaderValue("video/" + extension));
return response;
}
Where video.WriteToStream just reads the file and writes to output stream. Everything works great as I followed this article here.
This is how I'm using video.js to stream the video
<video id="really-cool-video" class="video-js vjs-default-skin" controls poster="#Model.MediaThumbnailUrl"
preload="auto" style="width: 100%; min-height: 380px; height: 100%;" autoplay
data-setup='{}'>
<source src="/api/Stream/myfilenamehere/mp4" type="video/mp4">
<p class="vjs-no-js">
To view this video please enable JavaScript, and consider upgrading to a web browser
that supports HTML5 video.
</p>
</video>
I am having an issue when I try to drag the video forward, it restarts the video. Earlier I had a similar issue that was due to I missed to set content-type for a block blob while uploading. Am I missing something here again?

Downloading a YouTube video through Wget

I am trying to download YouTube videos through Wget. The first thing necessary is to capture the URL of the actual video resource. Suppose I want to download this video: video. Opening up the page in the Firebug console reveals something like this:
The link which I have encircled looks like the link to the resource, for there we see only the video: http://www.youtube.com/v/r-KBncrOggI?version=3&autohide=1. However, when I am trying to download this resource with Wget, a 4 KB file of name r-KBncrOggI#version=3&autohide=1 gets stored in my hard-drive, nothing else. What should I do to get the actual video?
And secondly, is there a way to capture different resources for videos of different resolutions, like 360px, 480px, etc.?
Here is one VERY simplified, yet functional version of the youtube-download utility I cited on my another answer:
#!/usr/bin/env perl
use strict;
use warnings;
# CPAN modules we depend on
use JSON::XS;
use LWP::UserAgent;
use URI::Escape;
# Initialize the User Agent
# YouTube servers are weird, so *don't* parse headers!
my $ua = LWP::UserAgent->new(parse_head => 0);
# fetch video page or abort
my $res = $ua->get($ARGV[0]);
die "bad HTTP response" unless $res->is_success;
# scrape video metadata
if ($res->content =~ /\byt\.playerConfig\s*=\s*({.+?});/sx) {
# parse as JSON or abort
my $json = eval { decode_json $1 };
die "bad JSON: $1" if $#;
# inside the JSON 'args' property, there's an encoded
# url_encoded_fmt_stream_map property which points
# to stream URLs and signatures
while ($json->{args}{url_encoded_fmt_stream_map} =~ /\burl=(http.+?)&sig=([0-9A-F\.]+)/gx) {
# decode URL and attach signature
my $url = uri_unescape($1) . "&signature=$2";
print $url, "\n";
}
}
Usage example (it returns several URLs to streams with different encoding/quality):
$ perl youtube.pl http://www.youtube.com/watch?v=r-KBncrOggI | head -n 1
http://r19---sn-bg07sner.c.youtube.com/videoplayback?fexp=923014%2C916623%2C920704%2C912806%2C922403%2C922405%2C929901%2C913605%2C925710%2C929104%2C929110%2C908493%2C920201%2C913302%2C919009%2C911116%2C926403%2C910221%2C901451&ms=au&mv=m&mt=1357996514&cp=U0hUTVBNUF9FUUNONF9IR1RCOk01RjRyaG4wTHdQ&id=afe2819dcace8202&ratebypass=yes&key=yt1&newshard=yes&expire=1358022107&ip=201.52.68.216&ipbits=8&upn=m-kyX9-4Tgc&sparams=cp%2Cid%2Cip%2Cipbits%2Citag%2Cratebypass%2Csource%2Cupn%2Cexpire&itag=44&sver=3&source=youtube,quality=large&signature=A1E7E91DD087067ED59101EF2AE421A3503C7FED.87CBE6AE7FB8D9E2B67FEFA9449D0FA769AEA739
I'm afraid it's not that easy do get the right link for the video resource.
The link you got, http://www.youtube.com/v/r-KBncrOggI?version=3&autohide=1, points to the player rather than the video itself. There is one Perl utility, youtube-download, which is well-maintained and does the trick. This is how to get the HQ version (magic fmt=18) of that video:
stas#Stanislaws-MacBook-Pro:~$ youtube-download -o "{title}.{suffix}" --fmt 18 r-KBncrOggI
--> Working on r-KBncrOggI
Downloading `Sourav Ganguly in Farhan Akhtar's Show - Oye! It's Friday!.mp4`
75161060/75161060 (100.00%)
Download successful!
stas#Stanislaws-MacBook-Pro:~$
There might be better command-line YouTube Downloaders around. But sorry, one doesn't simply download a video using Firebug and wget any more :(
The only way I know to capture that URL manually is by watching the active downloads of the browser:
That largest data chunks are video data, so you can copy its URL:
http://s.youtube.com/s?lact=111116&uga=m30&volume=4.513679238953965&sd=BBE62AA4AHH1357937949850490&rendering=accelerated&fs=0&decoding=software&nsivbblmax=679542.000&hcbt=105.345&sendtmp=1&fmt=35&w=640&vtmp=1&referrer=None&hl=en_US&nsivbblmin=486355.000&nsivbblmean=603805.166&md=1&plid=AATTCZEEeM825vCx&ns=yt&ptk=youtube_none&csipt=watch7&rt=110.904&tsphab=1&nsiabblmax=129097.000&tspne=0&tpmt=110&nsiabblmin=123113.000&tspfdt=436&hbd=30900552&et=110.146&hbt=30.770&st=70.213&cfps=25&cr=BR&h=480&screenw=1440&nsiabblmean=125949.872&cpn=JlqV9j_oE1jzk7Zc&nsivbblc=343&nsiabblc=343&docid=r-KBncrOggI&len=1302.676&screenh=900&abd=1&pixel_ratio=1&bc=26131333&playerw=854&idpj=0&hcbd=25408143&playerh=510&ldpj=0&fexp=920704,919009,922403,916709,912806,929110,928008,920201,901451,909708,913605,925710,916623,929104,913302,910221,911116,914093,922405,929901&scoville=1&el=detailpage&bd=6676317&nsidf=1&vid=Yfg8gnutZoTD4G5SVKCxpsPvirbqG7pvR&bt=40.333&mos=0&vq=auto
However, for a large video, this will only return a part of the stream unless you figure out the URL query parameter responsible for stream range to be downloaded and adjust it.
A bonus: everything changes periodically as YouTube is constantly evolving. So, don't do that manually unless you carve pain.

Youtube "end=" embed tag not working?

I am trying to embed a Youtube video on my site with specific start and end times. Sites such as snipsnip.it and splicd.com use the start= and end= tags in the iframe src like so:
<iframe src='http://www.youtube.com/embed/OwjfE2ylbWU?start=5&end=10' width='640' height='360'>
</iframe>
However, this does not work on my web page. The video starts at the right time but then just plays till the end of the video. The Youtube API states that there is no "end=" tag, yet these sites all use it successfully.
Any idea on how to get embedded Youtube videos to end at a specific point?
splicd.com doesn't actually depend on the YouTube to stop the video. They poll the player with the following JavaScript and the YouTube Player API:
function checkYouTubePlayHead()
{
current = player.getCurrentTime();
if((current >= end) && splice) {
player.seekTo(start, true);=
player.pauseVideo();
}
if(current > start)
played = true;
}
It was working before without any js to implement, youtube just changed to googleapis video repository 2 days ago, and that messed up the end tag. It'll be fixed soon hopefully, not you're the only one, who need a solution for this. So far, this worked fine:
http://www.youtube.com/v/81hChAAt3So&start=107&end=115s&autoplay=0&autohide=0&theme=dark&color=white&rel=0&modestbranding=1&showinfo=0
Now most of the parameters are not passed. Be patient :)

Mobile Safari makes multiple video requests

I am designing a web application for iPad which makes use of HTML5 in mobile safari. I am transmitting the file manually through an ASP.NET .ashx file hosted on IIS 7 running .NET Framework v2.0.
The essential code looks partly like this:
// If we receive range header only transmit partial file
if (context.Request.Headers["Range"] != null)
{
var fi = new FileInfo(filePath);
long fileSize = fi.Length;
// Read start/end index
string headerRange = context.Request.Headers["Range"].Replace("bytes=", "");
string[] range = headerRange.Split('-');
int startIndex = Convert.ToInt32(range[0]);
int endIndex = Convert.ToInt32(range[1]);
// Add header Content-Range,Last-Modified
context.Response.StatusCode = (int)HttpStatusCode.PartialContent;
context.Response.AddHeader(HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderContentRange), String.Format("bytes {0}-{1}/{2}", startIndex, endIndex, fileSize));
context.Response.AddHeader(HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderLastModified), String.Format("{0:r}", fi.CreationTime));
long length = (endIndex - startIndex) + 1;
context.Response.TransmitFile(filePath, startIndex, length);
}
else
context.Response.TransmitFile(filePath);
Now what confuses me to no end is the the protocols for requesting that safari seems to use. From proxying the requests through fiddler i get the following for an aprox 2MB file.
NOTE: When requesting an mp4 file, directly served through IIS 7, the protocol and amount of request are the same
First it requests 2 bytes which allows it to read the 'Content-Range' header.
Now it request the entire content (?)
-
It proceeds to do step 1. & 2. again (??)
-
It now requests only parts of the file (???)
If the file is larger the last steps will be many more. I have tested up to 99 request where each request contains a part of the file equally split. This makes sense and is what would be expected I think. What I cannot make sense of is why it makes 2 initial request for the first 2 bytes as well as 2 requests for the entire file before it finally requests the file in different parts.
As I conclude this results in the file being downloaded between 2 - 3 times, depending on the length of the file and whether the user watches it long enough.
Can anybody make sense of this behavior and maybe explain what I can do to prevent multiple downloads. Thanks.
Per my comment to your question, I've had a similar issue in the past. One thing you could try if you have control of the server (I did not) is to disable either gzip or identity encoding of the file. I believe that in the first request for the entire content (#2 in your list) it asks for the content with gzip encoding (compressed). Perhaps you can configure your IIS to not to serve the file for a gzip-encoding request.
Here is my original (unanswered) question on the subject:
https://stackoverflow.com/questions/4855485/mpmovieplayercontroller-not-playing-full-length-mp3

Stream video from website, and support modern browsers (incl. IE) *and* iPad

My boss wants the following:
Requirements: Stream m4v videos from our Web-server to clients including standard web browsers (IE7, FF, Chrome, etc) and iPad!
I'm not really sure why he wants m4v...he mentioned efficiency but it may also have to do with iPad compatibility?? Anyway, I'm stuck with m4v.
I've browsed some related questions on SO, and this page is very useful as well:
http://henriksjokvist.net/archive/2009/2/using-the-html5-video-tag-with-a-flash-fallback
So if I understand correctly, HTML5 with <video> tag will take care of all my requirements (browsers & iPad) except IE up to and including IE8.
So in my code:
<div id="demo-video-flash">
<video id="demo-video" poster="snapshot.jpg" controls>
<source src="video.m4v" type="video/mp4" /> <!-- MPEG4 for Safari -->
<source src="video.ogg" type="video/ogg" /> <!-- Ogg Theora for Firefox 3.1b2 -->
</video>
</div>
<script type="text/javascript">
$(document).ready(function() { // ... a dash of jQuery.
var v = document.createElement("video"); // Are we dealing with a browser that supports <video>?
if ( !v.play ) { // If no, use Flash.
var params = {
allowfullscreen: "true",
allowscriptaccess: "always"
};
var flashvars = {
file: "video.f4v",
image: "snapshot.jpg"
};
swfobject.embedSWF("player.swf", "demo-video-flash", "480", "272", "9.0.0", "expressInstall.swf", flashvars, params);
}
});
</script>
As the link above explains, test if the browser supports <video>, and if not, fall back to flash. If the browser supports <video>, I don't need to worry about the player as the browser handles that. If it doesn't support <video>, I need to provide:
(a) A flash player.
(b) A flash-compatible copy of my .m4v video
Questions:
1) Will this solution work for my requirements?
2) Is .m4v a good format to stream to iPad? (I'm guessing yes as it's an Apple proprietary format!)
3) Is .m4v "flash-comatabile"? That is, if I send it to my flash player will it work? I've read conflicting reports on this. If it's not, then I guess I need to have a copy of my video converted to a flash-compatable format...any recommendations? (.f4v seems common but we already have a .mov file will that work?)
4) Last but not least, what's a good flash player. I'm leaning toward flowplayer (http://flowplayer.org/), however, we already have a swf player installed (http://code.google.com/p/swfobject/). Seems this latter one would work...any advantages to one or the other??
Apologies if some parts of this question don't make sense...there's alot of info about video out there and it's hard to piece it all together...hoping some answers here may help. I can refine my question as needed.
Thanks in advance!
Peter
As far as I know..., IE does not support HTML5 so the tag would be unrecognized in IE...

Resources