2015年2月2日月曜日

YouTube などのフルスクリーンで再生される UIWebView の動画をプログラムから終了させる方法

UIWebViewでyoutube.comやvine.coなどのサイトの動画を開いた場合、動画がフルスクリーンの専用動画プレイヤーViewControllerで再生されることがあります。このフルスクリーン動画を閉じる方法です。

方法1: 安全な方法

JavaScriptのFullscreen APIを使って安全に閉じることができます。iOS 6以上で動作確認済みです。
- (void)exitFullScreenVideo
{
// Works in iOS, not sure in OS X
[self.webView stringByEvaluatingJavaScriptFromString:@"Array.prototype.forEach.call(document.getElementsByTagName('video'),function(v){v.webkitExitFullscreen();});"];
// Didn't work in iOS, works in OS X
[self.webView stringByEvaluatingJavaScriptFromString:@"if(document.webkitFullscreenElement){document.webkitExitFullscreen();}"];
}

Fullscreen APIについては以下の資料が詳しいです。


方法2: animated, completionの制御もしたい場合

ここからがお待ちかねの黒魔法になります。
先ほどのJavaScriptを使った方法ではフルスクリーン動画を閉じる際のアニメーションを制御できません(必ずアニメーションが発生します)。またフルスクリーン動画を閉じ終わったタイミングのcompletion handlerが存在しません(JavaScriptでハンドルしようにも、videoタグのonendedイベントは通常の再生終了時にもイベントが飛ぶ上にアニメーション終了時ではなく再生終了時にイベントが飛ぶため使いづらい)。

通常はこれらが問題になることはまずありませんし、万一あったとしても仕様のほうを変えるほうが適切ですが、何らかの止むに止まれぬ理由によりなんとかしなければならなくなる場合が稀によくあります。

そこで画面上にUIWebView経由で表示されている動画プレイヤーViewControllerをビュー階層をたどって見つけ出し無理矢理dismissViewController:completion:で消すという方法を取ります。iOS 6以上にて動作確認済みです。ただしiOS 7以下の場合とiOS 8の場合で全く構造が異なり、将来にわたって動作するか非常に怪しいです。
/*
WARNING: PRIVATE API CALLS
WARNING: FORCEFUL MEMORY MANAGEMENT
highly recommend to avoid using this code
*/
- (void)exitFullScreenVideo:(BOOL)animated completion:(void (^)(void))completion
{
/*
iOS 6~7の場合
本ViewControllerの上にMPInlineVideoFullscreenViewControllerがfullscreen modalで表示されるような挙動になるので
それを通常通り閉じてやればよい
iOS 8の場合
新しいUIWindowが生成され、生成されたUIWindowのrootViewController.presentedViewControllerがAVFullScreenViewControllerになる
まずAVFullScreenViewControllerを閉じて、それからUIWindowのrootViewControllerをnilにセットしてhiddenにすることでUIWindowを消す
*/
if ([[[UIDevice currentDevice] systemVersion] doubleValue] >= 8.0) {
BOOL isFullScreenVideoContent = NO;
for (UIWindow *window in [UIApplication sharedApplication].windows) {
if ([window.rootViewController.presentedViewController isKindOfClass:NSClassFromString(@"AVFullScreenViewController")]) {
[window.rootViewController dismissViewControllerAnimated:animated completion:^{
window.rootViewController = nil;
window.hidden = YES;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[window performSelector:NSSelectorFromString(@"release")];
#pragma clang diagnostic pop
completion();
}];
isFullScreenVideoContent = YES;
break;
}
}
if (!isFullScreenVideoContent) {
completion();
}
} else {
if ([self.visibleViewController isKindOfClass:NSClassFromString(@"MPInlineVideoFullscreenViewController")]) {
[self dismissViewControllerAnimated:animated completion:completion];
} else {
completion();
}
}
}

Private APIの名前がバリバリ含まれるコードになっておりますので審査に出すアプリには使用しないことを強くおすすめさせていただきます。