はじめに
動画データを配信したいけれど、その時にデータを暗号化して
簡単にコピー利用できないようにしたい、という場合に。
今回の内容は、ファイルを一度ダウンロードさせる動作の想定です。
(ちなみにストリーミングの場合の暗号化も可能です)
MPMoviePlayer及びAVPlayerはinitWithDataがなく
NSDataから直接動画を再生する事ができません。
なので暗号化データから復号した一時ファイルを作り再生しようか...とも考えるのですが
せっかく暗号化したのに一時的にでも再生可能なファイルができてしまうのが不安です。
色々と調べた結果、一時ファイルを作らずに再生できる方法が分かりましたので共有します。
画面側
ViewController.h
//
// ViewController.h
// InMemorySample
//
// Created by Satoshi Hattori on 2015/06/23.
// Copyright (c) 2015年 Satoshi Hattori. All rights reserved.
//
@import AVKit;
#import <UIKit/UIKit.h>
@interface ViewController : AVPlayerViewController
@end
ViewController.m
//
// ViewController.m
// InMemorySample
//
// Created by Satoshi Hattori on 2015/06/23.
// Copyright (c) 2015年 Satoshi Hattori. All rights reserved.
//
#import <AVFoundation/AVFoundation.h>
#import "ViewController.h"
#import "CustomAVARLDelegate.h"
@interface ViewController ()
@property (nonatomic, strong) CustomAVARLDelegate *customAvARLDelegate;
@property (nonatomic, strong) AVPlayerItem *playerItem;
@end
@implementation ViewController
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.customAvARLDelegate = [[CustomAVARLDelegate alloc] init];
NSURL *url = [NSURL URLWithString:@"in-memory://sample1"];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
AVAssetResourceLoader *resourceLoader = asset.resourceLoader;
[resourceLoader setDelegate:self.customAvARLDelegate queue:dispatch_get_main_queue()];
// 遅延読み込みや読み込み完了通知など省略しています
self.playerItem = [[AVPlayerItem alloc] initWithAsset:asset];
self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem];
[self.player play];
}
@end
内部処理
CustomAVARLDelegate.h
//
// CustomAVARLDelegate.h
// InMemorySample
//
// Created by Satoshi Hattori on 2015/06/23.
// Copyright (c) 2015年 Satoshi Hattori. All rights reserved.
//
#import <AVFoundation/AVAssetResourceLoader.h>
@interface CustomAVARLDelegate : NSObject <AVAssetResourceLoaderDelegate>
@end
CustomAVARLDelegate.m
//
// CustomAVARLDelegate.m
// InMemorySample
//
// Created by Satoshi Hattori on 2015/06/23.
// Copyright (c) 2015年 Satoshi Hattori. All rights reserved.
//
#import "CustomAVARLDelegate.h"
#import "NSString+Data.h"
static NSString *customKeyScheme = @"in-memory";
static int badRequestErrorCode = 400;
@implementation CustomAVARLDelegate
- (void)reportError:(AVAssetResourceLoadingRequest *)loadingRequest withErrorCode:(int) error
{
[loadingRequest finishLoadingWithError:[NSError errorWithDomain:NSURLErrorDomain code:error userInfo:nil]];
}
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
NSString *scheme = [[[loadingRequest request] URL] scheme];
if ([self isCustomKeySchemeValid:scheme]) {
dispatch_async(dispatch_get_main_queue(), ^{
[self handleCustomKeyRequest:loadingRequest];
});
return YES;
}
return NO;
}
- (BOOL)isCustomKeySchemeValid:(NSString *)scheme
{
return ([customKeyScheme isEqualToString:scheme]);
}
- (BOOL)handleCustomKeyRequest:(AVAssetResourceLoadingRequest *)loadingRequest
{
// requestのURL内容からの分岐:
// アプリの要件に応じて作りかえてください。
// ここではファイル名を取得しました。
NSUInteger refIndex = [[NSString stringWithFormat:@"%@://", customKeyScheme] length];
NSString *fileName = [loadingRequest.request.URL.absoluteString substringFromIndex:refIndex];
// ファイル読み込み:
// 元からアプリのリソースに組み込んでいるmainBundleから暗号化済みデータを取得していますが
// アプリ管理領域内に保存したファイルの読み込みも可能です。
// ファイル内容からNSDataの作成:
// 一例として、テキストで読み取ったHexaDecimalの文字列からNSDataを生成しています。
// 実際には、暗号化データに復号処理を行いNSDataに落とし込みます。
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:@"dat"];
NSError *error;
NSString *text = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
NSData *data = [text decodeFromHexaDecimal];
if (data)
{
// 元データがmp4以外の場合、カスタマイズしてください。
loadingRequest.contentInformationRequest.contentType = @"public.mpeg-4";
loadingRequest.contentInformationRequest.contentLength = [data length];
loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;
NSData *requestedData = [data subdataWithRange:NSMakeRange((NSUInteger)loadingRequest.dataRequest.requestedOffset,
(NSUInteger)loadingRequest.dataRequest.requestedLength)];
[loadingRequest.dataRequest respondWithData:requestedData];
[loadingRequest finishLoading];
}
else {
[self reportError:loadingRequest withErrorCode:badRequestErrorCode];
}
return YES;
}
@end
こちらは本筋に関係のない物ですが。
NSString+Data.m
#import "NSString+Data.h"
@implementation NSString (Data)
unsigned char strToChar (char a, char b)
{
char encoder[3] = {'\0','\0','\0'};
encoder[0] = a;
encoder[1] = b;
return (char) strtol(encoder,NULL,16);
}
- (NSData *)decodeFromHexaDecimal
{
const char *bytes = [self cStringUsingEncoding: NSUTF8StringEncoding];
NSUInteger length = strlen(bytes);
unsigned char *r = (unsigned char *) malloc(length / 2 + 1);
unsigned char *index = r;
while ((*bytes) && (*(bytes +1))) {
*index = strToChar(*bytes, *(bytes +1));
index++;
bytes+=2;
}
*index = '\0';
NSData *result = [NSData dataWithBytes: r length: length / 2];
free(r);
return result;
}
@end
おわりに
AVAssetResourceLoader使うのか、と気付くまでに沢山時間を使ってしまいました...
参考になりましたら幸いです。
動画配信系アプリでお困りでしたら都内でしたら直接お話伺えます。
ご相談ください。
(お仕事の形で協力できると思います)