问题
I am writing a cordova app that lets the user record a video and upload it to youtube via their api.
If I use a file input and access the file via
$('#file').get(0).files[0]
I receive a file
object which is able to uploaded without a problem.
If I record a video I receive a medialist object.
I can then call window.resolveLocalFileSystemURL( video[0].localURL , success, fail);
On the success callback I receive a filelist object. Which also will not be accepted.
On this filelist
object I can call data.file(success,fail)
which finally returns me a file object. But when trying to upload to youtube I am getting a 308 error.
I suspect it has to do with permissions of accessing the local file path. If anyone has experience with this I would love to hear a way around it.
Here is the upload code:
UploadVideo.prototype.ready = function(accessToken, video) {
this.accessToken = accessToken;
this.gapi = gapi;
this.authenticated = true;
$this = this;
function result(data){
function success(data){
data.name = "VID_20150329_160037.mp4";
console.log(data)
$this.uploadFile( data );
}
data.file(success)
}
function fail(data){
console.log(data)
}
window.resolveLocalFileSystemURL( video[0].localURL , result, fail);
//this.uploadFile( $('#file').get(0).files[0] );
// $('#button').on("click", this.handleUploadClicked.bind(this));
回答1:
Using this blogpost I managed to get metadata and file upload working: http://lithostech.com/2013/10/upload-google-youtube-api-v3-cors/
By logging out the FileTransfer header information before it was sent, I could see the format was different:
Content-Disposition: form-data; name="part"
{"snippet":{"title":"Video title","description":"Video description","tags":"Video tags","categoryId":22},"status":{"privacyStatus":"unlisted"}}
Should be:
Content-Disposition: form-data; name=""; filename="file.json"
Content-Type: application/json
{"snippet":{"title":"Video title","description":"Video description","tags":"Video tags","categoryId":22},"status":{"privacyStatus":"unlisted"}}
Here is an updated version including working metadata:
function uploadVideo(fileURL) {
var options = new FileUploadOptions();
options.fileKey = 'file';
options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1);
options.mimeType = 'video/mpg';
options.chunkedMode = false;
options.headers = {
Authorization: 'Bearer ' + accessToken
};
options.params = {
"": {
snippet: {
title: 'Video title',
description: 'Video description',
tags: 'Video tags',
categoryId: 22
},
status: {
privacyStatus: 'unlisted'
}
}
};
var ft = new FileTransfer();
ft.upload(fileURL, 'https://www.googleapis.com/upload/youtube/v3/videos?part=snippet,status', function (data) {
console.log('upload success', data);
}, function (e) {
console.log('upload error', e);
}, options, true);
ft.onprogress = function (progressEvent) {
console.log('onprogress: ' + ((progressEvent.loaded / progressEvent.total) * 100) + '%');
};
}
note that I modified my FileTransfer plugin slightly too:
FileTransfer.java lines 374 - 376
beforeData.append("Content-Disposition: form-data; name=\"").append(key.toString()).append("\";");
beforeData.append(" filename=\"").append("file.json").append('"').append(LINE_END);
beforeData.append("Content-Type: ").append("application/json").append(LINE_END).append(LINE_END);
After modifying a plugin in cordova/phonegap/ionic you will need to reload it. I do this by removing the platform and adding it again:
cordova platform remove android; cordova platform add android;
回答2:
I finally got it working. I needed to use the cordova filetransfer plugin
here is the code
function postVideo(accessToken, fileURI) {
var metadata = {
snippet: {
title: "test",
description: "test",
tags: ["youtube-cors-upload"],
categoryId: 21
},
status: {
privacyStatus: "unlisted"
}
}
var options = new FileUploadOptions();
options.fileKey = "file";
options.fileName = 'test';
options.mimeType = "video/mp4";
options.chunkedMode = false;
options.headers = {
Authorization: "Bearer " + accessToken,
"Access-Control-Allow-Origin": "http://meteor.local"
};
var params = new Object();
params.part = Object.keys(metadata).join(',')
options.params = params;
console.log(options)
var ft = new FileTransfer();
ft.upload(fileURI, "https://www.googleapis.com/upload/youtube/v3/videos?part=snippet", win, fail, options, true);
ft.onprogress = function(progressEvent) {
if (progressEvent.lengthComputable) {
// console.log(progressEvent)
// loadingStatus.setPercentage(progressEvent.loaded / progressEvent.total);
} else {
console.log('something not loading')
// loadingStatus.increment();
}
console.log(progressEvent.loaded / progressEvent.total);
};
}
function win(r) {
console.log(r)
console.log("Code = " + r.responseCode);
console.log("Response = " + r.response);
console.log("Sent = " + r.bytesSent);
}
function fail(error) {
console.log(error)
// alert("An error has occurred: Code = " + error.code);
console.log("upload error source " + error.source);
console.log("upload error target " + error.target);
}
回答3:
Thank you for sharing information.
I had the same situation when I want to upload selected file in device by using the cordova plugin.
My case is not 308 error but it is the data of file which returned by cordova-file plugin have not right format as Blob format of input file HTML.
So that, There is simple solution to fix issue. It is convert data of file to right Blob format of input file HTML.
I have used sample from Youtube Data API and reference links as below:
- https://developers.google.com/youtube/v3/docs/videos/insert#examples
- https://github.com/youtube/api-samples/tree/master/javascript
The main idea of solution for fix this issue
1) Convert data of file to Blob format of input file HTML.
2) Modify content of upload_video.js file.
3) Modify content of cors_upload.js file.
The detail of solution
1) Convert data of file to Blob format of input file HTML:
○ Input content is file which returned by cordova-file plugin
○ Use FileReader HTML5 to read file content as ArrayBuffer.
○ Use Blob object to create new content with data is converted.
*Convert data sample code
UploadVideo.prototype.convertToBlob = function (file, onSuccess) {
var reader = new FileReader();
reader.onload = (function (event) {
var blob = new Blob([new Uint8Array(this.result)], { type: file.type });
onSuccess(blob);
});
reader.readAsArrayBuffer(file);
};
2) Modify content of upload_video.js file:
○ Create "myFile" property for UploadVideo object which store selected file.
○ Retrieve data content which returned by cordova-file plugin and convert it to Blob format.
*Read file content and convert content to Blob sample code
var self_ = this; //the instance of UploadVideo object
window.resolveLocalFileSystemURL(fileUri, function (fileEntry) {
//success
fileEntry.file(function(file){
//Store selected file to re-use when upload event
self_.myFile = file;
//We could use file as input of convertToBlob ()
self_.convertToBlob (file, function(blob){
self_.myFile.newBlob = blob;
});
});
}, function (response) {
//error
});
○ Need to sure new Blob content is already for input of this.uploadFile() in UploadVideo.prototype.handleUploadClicked() method.
*Modifiy source code in handleUploadClicked()
Replace: this.uploadFile($('#file').get(0).files[0]); By new: this.uploadFile(this.myFile);
3) Modify content of cors_upload.js file:
○ Modify new upload content from MediaUploader.prototype.sendFile_() method
*Modify source code in sendFile_()
Replace: var content = this.file; By new: var content = this.file.newBlob;
That is all my solution to fix this issue. With simple modification of source code.
Best Regards,
Dang Quynh.
回答4:
Continuing off of Kim T's answer, here's how to get it to work on iOS:
Modify the CDVFileTransfer.m
file located in the Plugins
directory of your xCode project with the code from this pull request. The modifications look like this:
src/ios/CDVFileTransfer.m
@@ -25,6 +25,7 @@ Licensed to the Apache Software Foundation (ASF) under one
+#import <Foundation/NSJSONSerialization.h>
@@ -203,6 +204,11 @@ - (NSURLRequest*)requestForUploadCommand:(CDVInvokedUrlCommand*)command fileData
+ // if it is a valid json object get the NSString representation
+ if ([NSJSONSerialization isValidJSONObject:val]){
+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:val options:NSJSONWritingPrettyPrinted error:nil];
+ val = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
+ }
Then in that same file, within the requestForUploadCommand
function as it iterates over the keys in options
, change this line:
[postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
To this:
[postBodyBeforeFile appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"file.json\"\r\nContent-Type: application/json\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
It probably doesn't matter, but I changed options:NSJSONWritingPrettyPrinted
to options:0
when using the code from the aforementioned pull request.
As soon as I made these changes, re-built the project in xCode and launched it on my wife's iPad, video upload to YouTube with meta tag information worked like a charm.
回答5:
The accepted answer is wrong and does not work. The correct answer is you should use the 2 part resumable upload method detailed in depth here: https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol
example:
var videoResourceYT = //yt video resource goes here
}
videoResourceYT = JSON.stringify(videoResourceYT)
var ftoptions = new FileUploadOptions();
var ft = new FileTransfer();
$.ajax({
url:"https://www.googleapis.com/upload/youtube/v3/videos?uploadType=resumable&part=snippet,status",
method:"POST",
data: videoResourceYT,
contentType:"application/json; charset=UTF-8",
beforeSend: function (request) {
request.setRequestHeader("Authorization", "Bearer " + token);
request.setRequestHeader("X-Upload-Content-Length", /*file length, not actually needed*/);
request.setRequestHeader("X-Upload-Content-Type", "video/*");
},
success: function(ytuploadData, ytTextStatus, ytRequest){
var nextUrlYT = ytRequest.getResponseHeader('Location');
console.log(nextUrlYT)
ftoptions.mimeType = "video/*";
ftoptions.chunkedMode = false;
ftoptions.headers = {
"Authorization": 'Bearer ' + token,
"Content-Type": "video/*"
};
ftoptions.httpMethod = "PUT"
//?part=snippet,status
ft.upload(/*options go here*/);
},
error: function(e1,e2,e3){
}
})
来源:https://stackoverflow.com/questions/29330360/youtube-upload-api-and-cordova-phonegap