[iOS]UIWebViewのVideoタグハンドリング

2014.04.21

videoタグイベントをどう取得するか

UIWebViewでHTML5を表示する際にvideoタグをタップするとデフォルトではフルスクリーンモードに遷移します。

フルスクリーンモード遷移イベントを取得する際にいろいろと躓いてしまったので、今回その対処法についてまとめていこうと思います。

undocumented notification

調べる中で最初に見つかった方法はフルスクリーンモードに関するNSNotificationをフックするというものでした。

How to receive NSNotifications from UIWebView embedded YouTube video playback - StackOverFlow

NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
[defaultCenter addObserver:self 
            selector:@selector(didEnterFullScreen:) 
                      name:@"UIMoviePlayerControllerDidEnterFullscreenNotification" 
                    object:nil];
[defaultCenter addObserver:self 
                  selector:@selector(didExitFullScreen:)
                      name:@"UIMoviePlayerControllerDidExitFullscreenNotification" 
                    object:nil];

このように書くとUIWebViewのビデオタグをタップし、フルスクリーン状態に入ったり出たりしたイベントをメソッドでフックできます。

しかし、この書き方は

@"UIMoviePlayerControllerDidEnterFullscreenNotification"
@"UIMoviePlayerControllerDidEnterFullscreenNotification"

が通知名としてAppleの公式ドキュメントに記載されていない為、アプリの中に組み込む際にはリジェクトの危険性をはらんでいます。エンタープライズアカウントで配布するアプリであればまだしも、AppStoreで配布するアプリの中では使わないほうが無難でしょう。

ビデオタグイベント

ということでビデオタグに関するイベントを取得するには別の方法を取る必要があります。まず思い立ったのはJavaScript側でビデオタグに関するイベントをフックして、それをUIWebViewに通知するという方法です。以下サンプルプロジェクトからの抜粋です。

videoplayendhandler.js

(function () {
    var scheme = 'videohandler://';

    var videos = document.getElementsByTagName('video');

    for (var i = 0; i < videos.length; i++) {
        videos[i].addEventListener('play', onPlay, false);
        videos[i].addEventListener('ended', onEnded, false);
    }

    function onPlay() {
        window.location = scheme + 'video-play';
    }

    function onEnded() {
        window.location = scheme + 'video-ended';
    }
})();

MYWebViewController.m

/**
 *  ビデオハンドラのためのダミーURLスキーム
 */
static NSString *const VideoHandlerScheme = @"videohandler";

#pragma mark - Lifecycle methods

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSString *htmlString = 
    [[NSBundle mainBundle] myVideoPageHTMLString];
    [self.webView loadHTMLString:htmlString
                         baseURL:[[NSBundle mainBundle] bundleURL]];
}

#pragma mark - UIWebViewDelegate

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *videoHandlerString = 
    [[NSBundle mainBundle] myVideoPlayEndHandlerJavaScriptString];

    if (videoHandlerString) {
        [webView stringByEvaluatingJavaScriptFromString:videoHandlerString];
    }
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
 navigationType:(UIWebViewNavigationType)navigationType
{    
    if ([request.URL.scheme isEqualToString:VideoHandlerScheme]) {
        NSLog(@"%@", request.URL);//こちらでイベントがフックできます。
        return NO;
    }

    return YES;
}

ビデオタグのイベント取得時に別に定めたURLスキームを持つURLに遷移するようにJavaScript側で決めておき、遷移のイベントをUIWebViewのデリゲードメソッドshouldStartLoadWithRequestで取得、特定スキームの時のみif文内のスコープが走ります。スコープ内でのURL名によってどのイベントが呼ばれたか知ることができます。

以上のような処理でUIWebViewで読み込んだHTMLのビデオタグの再生終了イベントのハンドリングは可能になります。ただし、これでは本来の目的であるフルスクリーンモードに遷移した時のイベントハンドリングができていません。

webkit固有のイベント

結論からいうと、フルスクリーンモードに入る時と出るときのビデオタグのイベントはWebKitの

webkitbeginfullscreen
webkitendfullscreen

イベントで取得できます。以下もサンプルプロジェクトからの抜粋です。

videofullscreenhandler.js

(function () {
    var scheme = 'videohandler://';

    var videos = document.getElementsByTagName('video');

    for (var i = 0; i < videos.length; i++) {
        videos[i].addEventListener('webkitbeginfullscreen', onBeginFullScreen, false);
        videos[i].addEventListener('webkitendfullscreen', onEndFullScreen, false);
    }

    function onBeginFullScreen() {
        window.location = scheme + 'video-beginfullscreen';
    }

    function onEndFullScreen() {
        window.location = scheme + 'video-endfullscreen';
    }
})();

MYWebViewController.m

#pragma mark - UIWebViewDelegate

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *videoHandlerString =
    [[NSBundle mainBundle] myVideoFullScreenHandlerJavaScriptString];

    if (videoHandlerString) {
        [webView stringByEvaluatingJavaScriptFromString:videoHandlerString];
    }
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 
 navigationType:(UIWebViewNavigationType)navigationType
{    
    if ([request.URL.scheme isEqualToString:VideoHandlerScheme]) {
        NSLog(@"%@", request.URL);//こちらでイベントがフックできます。
        return NO;
    }

    return YES;
}

iOSのUIWebViewはWebKitレンダリングエンジンを用いているため、このようなイベントハンドリングが可能になるというお話でした。

尚、フルスクリーンモードに移行するのを防ぐためにはUIWebViewの

@property (nonatomic) BOOL allowsInlineMediaPlayback

YESにセットすることで実現できます。

まとめ

以上の説明で用いたサンプルコードはGithubにあげています。JavaScriptとUIWebViewを連携するのはよくやることなのですが、webkit系のイベントはなかなか使う機会がなかったので一例という形で紹介しました。webkit系のイベントについてはiOSで使えそうなものについて別記事でまとめる機会があればと思います。

参考サイト