Reputation: 13
I am trying to attach a video stream using the .NET Mirror API, but I'm having some trouble.
I can't seem to find a method that supports the format referenced here:
POST /upload/mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}
Content-Type: multipart/related; boundary="mymultipartboundary"
Content-Length: {length}
--mymultipartboundary
Content-Type: application/json; charset=UTF-8
{ "text": "Skateboarding kittens" }
--mymultipartboundary
Content-Type: video/vnd.google-glass.stream-url
http://example.com/path/to/kittens.mp4
--mymultipartboundary--
The best info from Google I've seen is to use the following method to do the insert:
/// <summary>
/// Insert a new timeline item in the user's glass with an optional
/// notification and attachment.
/// </summary>
/// <param name='service'>Authorized Mirror service.</param>
/// <param name='text'>Timeline Item's text.</param>
/// <param name='contentType'>
/// Optional attachment's content type (supported content types are
/// "image/*", "video/*" and "audio/*").
/// </param>
/// <param name='attachment'>Optional attachment stream</param>
/// <param name='notificationLevel'>
/// Optional notification level, supported values are null and
/// "AUDIO_ONLY".
/// </param>
/// <returns>
/// Inserted timeline item on success, null otherwise.
/// </returns>
public static TimelineItem InsertTimelineItem(MirrorService service,
String text, String contentType, Stream attachment,
String notificationLevel) {
TimelineItem timelineItem = new TimelineItem();
timelineItem.Text = text;
if (!String.IsNullOrEmpty(notificationLevel)) {
timelineItem.Notification = new NotificationConfig() {
Level = notificationLevel
};
}
try {
if (!String.IsNullOrEmpty(contentType) && attachment != null) {
// Insert both metadata and media.
TimelineResource.InsertMediaUpload request = service.Timeline.Insert(
timelineItem, attachment, contentType);
request.Upload();
return request.ResponseBody;
} else {
// Insert metadata only.
return service.Timeline.Insert(timelineItem).Fetch();
}
} catch (Exception e) {
Console.WriteLine("An error occurred: " + e.Message);
return null;
}
}
However, this code takes the content to "attach" as a stream (which is great for, say, uploading an image, which I've tested and had work). But, a streaming video requires only the URL of the video.
I've tried sending in the string representation of the URL as a stream, but the result is just a video that loads indefinitely.
I've successfully been able to get the video to play by making a cURL request using my auth token and the POST request above, so I know the video itself isn't the issue.
Has anyone been able to get streaming video to work through .NET (either with the Mirror API or with a custom WebRequest of some sort?) I've tried creating the WebRequest myself from scratch, but I'm getting 400's as a response.
For reference, the other code I've tried:
var request = WebRequest.CreateHttp(baseAddress + method);
request.Method = "POST";
request.Headers.Add("Authorization", string.Format("Bearer {0}", auth));
string itemJson = JsonConvert.SerializeObject(item.Item, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
string contentFormat = "--MyBound\nContent-Type: application/json; charset=UTF-8\n\n{0}\n--MyBound\nContent-Type: video/vnd.google-glass.stream-url\n\n{1}\n--MyBound--";
string content = string.Format(contentFormat, new[] { itemJson, item.VideoUrl });
request.ContentLength = content.Length;
request.ContentType = "multipart/related; boundary=\"MyBound\"";
var rs = request.GetRequestStream();
using (var sw = new StreamWriter(rs))
{
sw.Write(content);
}
var response = request.GetResponse();
Where item is a class I've written that contains the VideoUrl as a string, and the Item (a TimelineItem from the Mirror API), and where the video Url I'm using is:
http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8
Thanks in advance, everyone!
Upvotes: 1
Views: 724
Reputation: 66
Sanath's code is fine for small files, but you really don't want to be doing a binary upload of anything large to Glass.
The documentation on the Glass site is kind of misleading, they go on at length about how to do multi-part uploads, but then tell you that they aren't a good idea and briefly mention how you should do it.
Glass actually supports progressive download and hls streaming directly from the timeline. You'll want to create a standard image card with a thumbnail reference then add the PLAY_VIDEO menu item to your list of menu items It's been a while since I've done any .net programming, but I'm guessing this should work.
new MenuItem() {Action = "PLAY_VIDEO", Payload = mediaLink}
Upvotes: 1
Reputation: 493
I have success with following code.
String mediaLink = "url_to_your_video.mp4";
String message = "you_message";
MirrorService Service = new MirrorService(new BaseClientService.Initializer()
{
Authenticator = Utils.GetAuthenticatorFromState(state)
});
TimelineItem timelineItem = new TimelineItem();
timelineItem.Creator = new Contact()
{
Id = Config.CREATOR_ID,
DisplayName = Config.DISPLAY_NAME,
};
timelineItem.Notification = new NotificationConfig() { Level = "DEFAULT" };
timelineItem.MenuItems = new List<MenuItem>()
{
new MenuItem() {Action = "NAVIGATE"},
new MenuItem() {Action = "DELETE"},
new MenuItem() {Action = "SHARE"},
};
if (!String.IsNullOrEmpty(mediaLink))
{
Stream stream = null;
if (mediaLink.StartsWith("/"))
{
stream = new StreamReader(Server.MapPath(mediaLink)).BaseStream;
}
else
{
HttpWebRequest request = WebRequest.Create(mediaLink) as HttpWebRequest;
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
byte[] b = null;
using (Stream streamFromWeb = response.GetResponseStream())
using (MemoryStream ms = new MemoryStream())
{
int count = 0;
do
{
byte[] buf = new byte[1024];
count = streamFromWeb.Read(buf, 0, 1024);
ms.Write(buf, 0, count);
} while (streamFromWeb.CanRead && count > 0);
b = ms.ToArray();
stream = new MemoryStream(b);
}
}
Service.Timeline.Insert(timelineItem, stream, "video/mp4").Upload();
}
else
{
Service.Timeline.Insert(timelineItem).Fetch();
}
Upvotes: 1
Reputation: 2963
An infinite load can mean the video was not the correct format or no longer is being served. I don't think this is the case here.
The video URL you mention is the same one I can get to work using Curl as documented in this answer:
Attaching video with video/vnd.google-glass.stream-url after Update XE6
(look for my answer, which is not the selected one)
This means that there is something wrong in your request, what is the response? Here is sample response when I send a working request:
{
"kind": "mirror#timelineItem",
"id": "44359ebc-ff49-4d48-a609-2f6ab1354ae3",
"selfLink": "https://www.googleapis.com/mirror/v1/timeline/44359ebc-ff49-4d48-a
609-2f6ab1354ae3",
"created": "2013-07-13T05:05:30.004Z",
"updated": "2013-07-13T05:05:30.004Z",
"etag": "\"ZECOuWdXUAqVdpmYErDm2-91GmY/h_jXHSw50TrLSr94HZGFIGAlPxs\"",
"text": "Sweetie",
"attachments": [
{
"id": "bs:9088a6e2-b8ad-4e1d-a544-5d7e858e5e3f",
"contentType": "video/vnd.google-glass.stream-url",
"contentUrl": "https://www.googleapis.com/mirror/v1/timeline/44359ebc-ff49-4d
48-a609-2f6ab1354ae3/attachments/bs:9088a6e2-b8ad-4e1d-a544-5d7e858e5e3f?alt=med
ia"
}
]
}
I just did this and do see the beep - bop video play on Glass.
So, check the response, and also see if you can print out the request, mine looks like this:
--mymultipartboundary
Content-Type: application/json; charset=UTF-8
{ "text": "Sweetie" }
--mymultipartboundary
Content-Type: video/vnd.google-glass.stream-url
http://devimages.apple.com/iphone/samples/bipbop/gear1/prog_index.m3u8
--mymultipartboundary--
One way to see the request is to use a a sniffer like Charles, Fiddler or Wireshark, if that isn't working for you point your request at a php file like this then look at out.txt (note my php isn't great so you might have to modify this):
<?php
$file = 'out.txt';
$current .= print_r($_GET, true);
$current .= print_r($_POST,true);
$current .= print_r(getallheaders(),true);
file_put_contents($file, $current);
?>
I think you should focus on the second section of code you posted, it looks very close to working to me, just print out some of those items and it should be clear by comparison to my examples what is going wrong.
Upvotes: 0