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!
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();
}
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}
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.
Related
How do I use Azure AD Graph to update values on the AdditionalValues dictionary for a user? The test below returns 400 Bad Response.
Background:
The rest of my application uses MSGraph. However, since a federated user can not be updated using MSGraph I am searching for alternatives before I ditch every implementation and version of Graph and implement my own database.
This issue is similar to this one however in my case I am trying to update the AdditionalData property.
Documentation
[TestMethod]
public async Task UpdateUserUsingAzureADGraphAPI()
{
string userID = "a880b5ac-d3cc-4e7c-89a1-123b1bd3bdc5"; // A federated user
// Get the user make sure IsAdmin is false.
User user = (await graphService.FindUser(userID)).First();
Assert.IsNotNull(user);
if (user.AdditionalData == null)
{
user.AdditionalData = new Dictionary<string, object>();
}
else
{
user.AdditionalData.TryGetValue(UserAttributes.IsCorporateAdmin, out object o);
Assert.IsNotNull(o);
Assert.IsFalse(Convert.ToBoolean(o));
}
string tenant_id = "me.onmicrosoft.com";
string resource_path = "users/" + userID;
string api_version = "1.6";
string apiUrl = $"https://graph.windows.net/{tenant_id}/{resource_path}?{api_version}";
// Set the field on the extended attribute
user.AdditionalData.TryAdd(UserAttributes.IsCorporateAdmin, true);
// Serialize the dictionary and put it in the content of the request
string content = JsonConvert.SerializeObject(user.AdditionalData);
string additionalData = "{\"AdditionalData\"" + ":" + $"[{content}]" + "}";
//additionalData: {"AdditionalData":[{"extension_myID_IsCorporateAdmin":true}]}
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage
{
Method = HttpMethod.Patch,
RequestUri = new Uri(apiUrl),
Content = new StringContent(additionalData, Encoding.UTF8, "application/json")
};
var response = await httpClient.SendAsync(request); // 400 Bad Request
}
Make sure that the Request URL looks like: https://graph.windows.net/{tenant}/users/{user_id}?api-version=1.6. You need to change the api_version to "api-version=1.6".
You cannot directly add extensions in AdditionalData and it will return the error(400).
Follow the steps to register an extension then write an extension value to user.
Register an extension:
POST https://graph.windows.net/{tenant}/applications/<applicationObjectId>/extensionProperties?api-version=1.6
{
"name": "<extensionPropertyName like 'extension_myID_IsCorporateAdmin>'",
"dataType": "<String or Binary>",
"targetObjects": [
"User"
]
}
Write an extension value:
PATCH https://graph.windows.net/{tenant}/users/{user-id}?api-version=1.6
{
"<extensionPropertyName>": <value>
}
I have an HTTP Action in Azure Logic Apps that calls a StackExchange API, fundamentally, it could be any API that returned GZip or Deflate content by default:
Because the response is neither Plain Text nor JSON the output from the HTTP Action is:
{
"$content-encoding": "gzip",
"$content-type": "application/json; charset=utf-8",
"$content": "H4sIAAAAAAAEAGWPzY7CMAyE38XnqsrPFtq+ClpFoXghEkm6iVtWQrw7Lt3mAEd/Hs+M7+AIfYb+cIeAN2MHcjOaKWNiKCqgSPa6zZ2SFRzt6YzZjJiMd2EiZF0tvjbpuoZe7rrdxuZIC9KNLo5D9B4DLUIl2gpsyDfOeLflvN8JM7kYPnbF6/+WA/ZNYcOAI+Hp5V/eCKv0heVGSwD0SnRcZXQm4ewyM+hBCbmvda3aWjVaS3h8V3Cx2fiYuMePvWZcSrKV8faPSyzF1jmhty64cGbnVjyeRIHcnG0BAAA="
}
If you went to the trouble of passing the $content field through #base64toString() you would end up with the Gzip binary representation of the JSON, and that is as far as I can take it.
Question: How can I either force the HTTP Action to behave like a HttpClient and Accept GZip data and emit the JSON from the Action, or more laboriously take the GZip Base64/Binary data and decompress it before acting further upon it?
I wasn't able to find a way to "solve" the problem directly, so I created an Azure Function that made the request to the URL with the appropriate HttpClientHandler passed into the HttpClient:
#r "Newtonsoft.Json"
using System;
using System.Net;
using Newtonsoft.Json;
public static async Task<object> Run(HttpRequestMessage req, TraceWriter log) {
string jsonContent = await req.Content.ReadAsStringAsync();
dynamic data = JsonConvert.DeserializeObject(jsonContent);
if (data.url == null) {
return req.CreateResponse(HttpStatusCode.BadRequest, new {
error = "Please pass a URL in the input object"
});
}
HttpClientHandler handler = new HttpClientHandler() {
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
var client = new HttpClient(handler);
var result = await client.GetAsync((string)data.url);
var response = req.CreateResponse(HttpStatusCode.OK);
response.Content = result.Content;
return response;
}
By swapping the built-in HTTP Action from the original question, I was able to get this working:
And the configuration for the Azure Function is as follows:
I watch a gcs bucket with watchbucket command. After that i am doing a upload. The watch command sends a notification to my servlet on app engine.
To be sure about that, i have a look at the logs. But it seems so, that some how the servlet keeps on getting request after request. Four times successfully and after that it gets an nullpointer exception. Why are there so many requests?
* EDIT *
On client side i use a meteorJS a Javascript framework. I added the extension slingshot to process the upload.
first i have to provide a the necessary info like acl, bucket, etc. like this:
Slingshot.createDirective('uploadSpotCover', Slingshot.GoogleCloud, {
bucket: 'upload_spot_cover',
GoogleAccessId: 'accessId',
acl: 'project-private',
maxSize: 0,
cacheControl: 'no-cache',
...
}
As you can see in line 153 slingshot uses a XMLHttpRequest to upload to Cloudstorage
https://github.com/CulturalMe/meteor-slingshot/blob/master/lib/upload.js#L144
On serverside my Servlet and it's logic look like this.
public class Upload extends HttpServlet {
private BucketNotification notification;
#Override
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
this.notification = getBucketNotification(req);
UploadObject object = new UploadObject(notification);
CloudStorageHandler gcs = new CloudStorageHandler();
BlobstoreHandler bs = new BlobstoreHandler();
ImageTransformHandler is = new ImageTransformHandler();
/** GET DATA FROM GCS **/
byte[] data = gcs.getFileFromGoogleCloudStorage(object.getGcsBucket(), object.getGcsPath());
//BlobKey bk = bs.createBlobKey(object.getGcsBucket(), object.getGcsPath());
/******************/
/** TRANSFORMATION **/
byte[] newImageData = is.resizePicture(data, 1200, 1200, "JPEG", 65, 0.5, 0.5);
/******************/
/** STORE NEW RESIZED FILE INTO GCS BUCKET **/
UploadObject tmp = new UploadObject(object.getGcsPath(), "JPEG", "beispiel", 1200, 1200);
tmp.setData(newImageData);
gcs.saveFileToGoogleCloudStorage(newImageData, "beispiel", object.getGcsPath(), "JPEG", "public-read");
/******************/
/** CREATE SERVING URL via BLOBKEY **/
BlobKey bk_neu = bs.createBlobKey("beispiel", object.getGcsPath());
String servingUrl = is.createServingUrlWithBlobkey(bk_neu);
/******************/
log("Blobkey: "+ bk_neu.getKeyString());
log("url: "+ servingUrl);
res.setStatus(200);
}
private BucketNotification getBucketNotification(HttpServletRequest req) {
BucketNotification notification = null;
String jsonString ="";
try {
jsonString ="";
BufferedReader in = new BufferedReader(new InputStreamReader(req.getInputStream()));
for (String buffer;(buffer = in.readLine()) != null;jsonString+=buffer + "\n");
in.close();
notification = new Gson().fromJson(jsonString, BucketNotification.class);
} catch (IOException e) {
log("Failed to decode the notification: " + e.getMessage());
return null;
}
return notification;
}
}
I wrapped the specific service methods like save file to cloudstorage in its own handlers.
Because I do not know what you are using to upload, I'm guessing now. Most file uploads upload stuff in bulks, so not the whole file at once, but in smaller chunks until everything is up. This would explain the multiple calls you are getting.
Can you show us the server and the client code? Maybe that way we could see the problem.
I am trying to parse the response atom feed received from websphere portal after rest call using apache abdera. However receiving the below error when parsing. Could some one let me know what the issue is?
org.apache.abdera.parser.stax.FOMUnsupportedTextTypeException: Unsupported Text Type: text/html
Abdera abdera = new Abdera();
AbderaClient abderaClient = new AbderaClient(abdera);
Factory factory = abdera.getFactory();
Cookie[] cookies=request.getCookies();
org.apache.commons.httpclient.Cookie ltpaCookieHttpCommons = new org.apache.commons.httpclient.Cookie();
RequestOptions options = new RequestOptions(true);
List<String> cookieStrings = new ArrayList<String>();
options.setHeader("Cookie", (String[])cookieStrings.toArray(new String[cookieStrings.size()]));
ClientResponse resp = abderaClient.get("http://localhost:10039/wps/contenthandler/!ut/p/digest!W9TQFjuU7ADCwtSkxDsxHg/searchfeed/search?queryLang=en&locale=en&resultLang=en&query=test&scope=com.ibm.lotus.search.ALL_SOURCES&start=0&results=10&output=application/xml", options);
System.out.println(resp.getType());
if (resp.getType() == ResponseType.SUCCESS) {
System.out.println("!!!!!!Response success!!!!!!");
Document<Feed> docFeed = resp.getDocument();
// JSON Output
Writer writer = abdera.getWriterFactory().getWriter("json");
try {
Feed feed=docFeed.getRoot();
abdera.getWriterFactory().getWriter("json").writeTo(feed, System.out);
} catch(Exception e) {
e.printStackTrace();
}
} else {
}
The issue is that the atom feed your parsing has a type tag with text/html in it which isn't in the atom spec so abdera throws the above error.
According to the spec:
When present, the value
MUST be one of "text", "html", or "xhtml"
Are you sure the feed is an atom feed and not an RSS feed which supports enclosures with MIME types like the one above ?
Description:
I have multiple successive image downloads and saving in IsolatedStorage using HttpWebRequest.
After all image downloadsa are completed I need to navigate user to another page, where images are displayed in image controls from isolated storage.
Question:
How can I know when all the downloads are completed to run the navigation?
I tried to pass the redirect to the requests callback function (requestImage_BeginGetResponse()) in the last foreach loops iteration after saving the image,
but the images are different sizes and sometimes the last image downloads faster than previous, that results in redirect before all downloads are completed.
the code:
private HttpWebRequest request;
private void downloadDataFile()
{
...
foreach (Gallery image in gallery)
{
request = (HttpWebRequest)WebRequest.Create(image.url);
request.BeginGetResponse(new AsyncCallback(requestImage_BeginGetResponse), new object[] { request, image.name });
}
}, request);
}
private void requestImage_BeginGetResponse(IAsyncResult r)
{
object[] param = (object[])r.AsyncState;
HttpWebRequest httpRequest = (HttpWebRequest)param[0];
string filename = (string)param[1];
HttpWebResponse httpResoponse = (HttpWebResponse)httpRequest.EndGetResponse(r);
System.Net.HttpStatusCode status = httpResoponse.StatusCode;
if (status == System.Net.HttpStatusCode.OK)
{
Stream str = httpResoponse.GetResponseStream();
Deployment.Current.Dispatcher.BeginInvoke(new Action(() =>
{
saveImage(str, filename);
}));
}
}
You should prepare a int type variable to record your images that would be downloaded.Whenever a image is downloaded,make the variable minus 1 untill its value is 0,and notify the navigating operation.