Кордовое внешнее приложение + местное видео
У нас есть приложение iOS, построенное с помощью PhoneGap/Cordova 4.3.0. Это приложение напрямую загружает внешний веб-сайт, используя <content src="http://example.com/foo" />
в файле config.xml
. Все функциональные возможности содержатся на этом веб-сайте, поэтому мы фактически не используем какие-либо локальные файлы HTML или JS.
Как часть функциональности приложения, мы должны воспроизвести некоторые видео. Поскольку приложение предназначено для работы в автономном режиме, мы хотим кэшировать эти видеоролики локально. Поэтому мы загружаем их на устройство с помощью плагина FileTransfer вместе с другими ресурсами, такими как изображения или PDF файлы. После загрузки файлов мы получаем URL-адреса с протоколом file://
. У нас также есть возможность использовать протокол cdvfile://
. Когда мы используем URL cdvfile://
для показа изображений, изображения отображаются правильно. Однако видео не воспроизводится вообще.
Для воспроизведения видео мы используем стандартные теги HTML5:
<video width="auto" height="100%" controls="controls" autoplay="true">
<source src="..." type="video/mp4" />
</video>
Видео сами работают, и они будут играть правильно из внешнего источника (как в, они будут воспроизводиться, если мы обращаемся к ним с сервера, а не из локальной файловой системы). Я понимаю, что проблема связана с концепциями, связанными с веб-сайтами, такими как политика одного и того же происхождения и с ограничением доступа к локальной файловой системе. Тем не менее, в то же время я должен задаться вопросом, почему именно изображения отлично работают при этих же ограничениях.
То, что я пробовал до сих пор:
- Использование
file://
и cdvfile://
URL-адресов в качестве src
видео. Это не дает никакого визуального эффекта. Экран просто черный.
- Использование
iframe
с src
, установленным для URL-адреса видео. При использовании file://
экран по-прежнему остается черным. Однако при использовании cdvfile://
появляется интерфейс проигрывателя iOS с кнопкой воспроизведения и полноэкранной кнопкой, но видео не воспроизводится, и также нет временной шкалы.
- Добавление локального файла в кордову под названием
video.html
, который принимает URL-адрес в качестве параметра и отображает тег video
с этим URL как src
. План должен был включить этот файл как iframe
, но, видимо, я не могу сделать iframe
для локального файла. Я пробовал различные URL-адреса, которые могли бы указывать на этот конкретный файл video.html
(хотя на самом деле я не уверен, что это возможно). Среди тех, которые я пробовал, были: cordova.file.applicationDirectory + 'www/video.html'
, http://localhost/www/video.html
, cdvfile://localhost/www/video.html
.
- Я искал плагин cordova, который будет воспроизводить видео, но я не смог найти его для iOS. Большинство плагинов, похоже, нацелены на Android.
Теперь, возможно, я ошибаюсь в этом. Как я вижу, "стандартным вариантом использования" для кордовой является то, что вы храните свои файлы HTML/JS/CSS локально. Внешний контент, подобный тому, который я использую, вероятно, немного необычен. Я объясню требования к этому приложению, которые привели меня к использованию этой функции.
- Предполагается, что приложение будет создано для нескольких платформ (хотя мы и начинаем с iOS). Поэтому мы используем PhoneGap.
- Предполагается, что он доступен как онлайн, так и офлайн, хотя весь контент поступает с сервера (контент не создается локально). Вот почему мы загружаем контент и сохраняем его локально.
- Также предполагается автоматическое обновление любой части себя на лету, не требуя обновления из App Store. Вот почему мы используем внешнюю страницу, потому что она имеет
cache.manifest
, которая позволяет нам контролировать обновления кода веб-приложения и в то же время разрешать его кэширование локально. Это, наверное, самое главное, потому что, если мы хотим сохранить некоторые файлы локально в Кордове, нам придется повторно реализовать эту функциональность кеша в Javascript (используя как можно более тонкий слой).
В любом случае, моя главная проблема заключается в том, как заставить эти видео работать. Я готов попробовать самые взломанные обходные пути! Если это действительно невозможно с текущими решениями по развитию, то, возможно, вы могли бы дать мне несколько советов о том, как я должен структурировать приложение, чтобы он работал и выполнял мои требования.
Большое спасибо!
Ответы
Ответ 1
У меня был аналогичный проект около года назад. Но я не помню, как я столкнулся с этой проблемой, потому что мы связали наши html/js/css с приложением.
Проблема в том, что вы пытаетесь загрузить файл:///url протокола из html файла, который был из http:///, который не является чем-то удобным для UIWebView.
Вы можете обойти это с помощью специальной схемы URL, например video://, но вам нужно написать собственный код, который перехватывает эти запросы и передает фактическое видео обратно в систему загрузки URL.
Конечный результат:
![The end result]()
Вот как я это сделал с помощью Cordova 4.3.0 и немного ObjectiveC
- Создайте новый класс Objective C с именем VideoURLProtocol, который расширяет NSURLProtocol:
VideoURLProtocol.h:
#import <Foundation/Foundation.h>
@interface VideoURLProtocol : NSURLProtocol <NSURLConnectionDelegate>
@property (strong, nonatomic) NSURLConnection *connection;
@end
VideoURLProtocol.m:
#import "VideoURLProtocol.h"
@implementation VideoURLProtocol
@synthesize connection;
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return [[[request URL] absoluteString] rangeOfString:@"video://"].location != NSNotFound;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
- (void)startLoading {
NSString *currentURL = [[self.request URL] absoluteString];
NSString *newURL = [currentURL stringByReplacingOccurrencesOfString:@"video://" withString:@"file:///"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:newURL]];
self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)stopLoading {
[self.connection cancel];
self.connection = nil;
}
@end
-
Добавьте следующую строку в метод didFinishLaunchingWithOptions приложения AppDelegate.m
.
.
// These lines should already be there
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
// This line
[NSURLProtocol registerClass:[VideoURLProtocol class]];
.
.
-
И вот код javascript, который использует эту новую схему URL
document.addEventListener('deviceready', function(){
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem){
var caches = fileSystem.root.nativeURL.replace("Documents", "Library/Caches"),
videoPath = caches + "video.mp4";
new FileTransfer().download("http://clips.vorwaerts-gmbh.de/VfE_html5.mp4", videoPath, function(entry){
document.getElementsByTagName("video")[0].src = caches.replace("file:///", "video://") + "video.mp4"
}, function(error){
alert("unable to download file: " + error);
});
});
}, false);
Некоторые дополнительные моменты, о которых стоит упомянуть:
Обратите внимание, что в моем javascript-коде я загружаю файл в "/Library/Caches" вместо каталога "/Documents" (по умолчанию), потому что каталог "/Documents" подкрепляется iCloud и Apple отклоняет приложения, которые пытаются выполнить резервное копирование более ~ 100 МБ. Это то, что я нашел трудным путем после отклонения моего приложения.
Вы можете увидеть пространство, занятое вашим приложением: Настройки > iCloud > Хранение > Управление хранилищем > {{Ваше iphone name}} > Показать все приложения
Вы можете автозапускать видео, добавив следующую строку в свой config.xml
<preference name="MediaPlaybackRequiresUserAction" value="false" />
Вы также можете воспроизводить видеоролики, добавив следующую строку в свой файл config.xml, в дополнение к этому вам также нужно добавить атрибут webkit-playinginline = "true" к вашему видео:
<preference name="AllowInlineMediaPlayback" value="true" />
<video controls="controls" autoplay="true" webkit-playsinline="true" preload="auto">
</video>
Вот действительно интересный учебник от Ray, который объясняет NSURLProtocol очень подробно: http://www.raywenderlich.com/59982/nsurlprotocol-tutorial
Ответ 2
В моем опыте использование протокола file://
было проблематичным для iOS, потому что протокол начинается с корня файловой системы устройства.
Я не считаю, что здесь возникают проблемы Cross-Origin, потому что Cordova не выполняет запросы Cross-Origin, а рассматривает все запросы как исходящие от источника, к которому они обращаются. См. этот ответ.
Мое теоретическое решение использует URL relative вместо того, чтобы вообще пытаться использовать какой-либо протокол. Однако способ, которым вы это реализуете, может зависеть от того, когда файл будет успешно загружен.
<video width="auto" height="100%" controls="controls" autoplay="true">
<source src="/localhost/www/video.mp4" type="video/mp4" />
</video>
Где cdvfile://localhost/www/
- это путь, который вы задали для аргумента target
, когда вы вызывали fileTransfer.download()
на который ссылается здесь.
Может потребоваться либо создать элемент видео, либо установить видео src в javascript после того, как был запущен successCallback
. Снова вы должны установить src как относительный URL.
Обратите внимание, что видеоролики не будут автоматически запускаться на мобильных устройствах
Из библиотеки разработчиков Safari
В Safari на iOS (для всех устройств, включая iPad), где пользователь может находиться в сотовой сети и заряжаться на единицу данных, предварительная загрузка и автовоспроизведение отключены.