AVFoundationを利用した動画表示用の便利クラス (使い方)

前回UPした動画再生クラス MovieView の使い方です
今回はサンプルとしてスライダーで再生位置の調整も行えるようにしてみました。

プライベートな変数にこんなのを用意して

UISlider* _slider;
MovieView* _movieView;

ビューの生成と破棄


-(void)dealloc
{
    if(_movieView){
        _movieView.delegate = nil;
        [_movieView removeFromSuperview];
        _movieView = nil;
    }
       
    [super dealloc];
}


- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

     //スライダーの生成
     _slider = [[[UISlider alloc]initWithFrame:CGRectMake(0, 0, 300, 40)]autorelease];
        [self addSubview:_slider];
        [_slider addTarget:self action:@selector(seekBarValueChanged:) forControlEvents:UIControlEventValueChanged];

     //動画再生用Viewの生成
     _movieView = [[[MovieView alloc]initWithFrame:self.bounds]autorelease];
        _movieView.delegate = self;     //※再生だけならdelegateの設定は不要ですよ。
        [self addSubview:_movieView];
       
     //リソースからムービーのURLを取得して
        NSURL *url = [[NSBundle mainBundle] URLForResource:@"opening" withExtension:@"mov"];

     //再生開始
     [_movieView playMovie: url];
    }
    return self;
}

各種イベントのハンドリング


//スライダーからのコールバック
- (void)seekBarValueChanged:(UISlider *)slider
{    
    //ムービーのシーク
    float val = slider.value;
    float duration = _movieView.movieDuration;
    float time = val * duration / _slider.maximumValue;

    [_movieView seekToSeconds:time];   
}


#pragma mark - MovieViewDelegate

//再生開始時の処理
-(void)movieView:(MovieView*)sender movieWillPlayItem:(AVPlayerItem*)playerItem duration:(Float64)duration
{
    //スライダーを初期位置に変更
    _slider.minimumValue = 0;
    _slider.maximumValue = duration;
    _slider.value        = 0;
   
}

//再生が進んだ時に順次呼ばれる
-(void)movieView:(MovieView*)sender moviePlayAtTime:(Float64)time duration:(Float64)duration
{
    //スライダーの位置を再生時間に合わせる
    float value = _slider.maximumValue * time / duration;
    _slider.value = value;
}

 
以上のような感じです。
スライダー関連の処理が多くなってしまいましたが、単純に再生するだけなら

_movieView = [[[MovieView alloc]initWithFrame:self.bounds]autorelease];
[self addSubview:_movieView];

NSURL *url = [[NSBundle mainBundle] URLForResource:@"opening" withExtension:@"mov"];
[_movieView playMovie: url];

これだけでいけます。

AVFoundationを利用した動画表示用の便利クラス

チュートリアルの一部やオープニング画面など、アプリ内で部分的にムービーを表示したい事ありますよね。

MPMoviePlayerControllerを使えば、至れり尽くせりで超簡単!なんですけど、UIを自分で作ったり細かい制御をしたい場合にはやはりAVFoundationを使った方が良さそうです。
AVFoundationを使ってもそんなに難しい処理ではないんですけど、表示アイテムの管理や各種Observerの管理などでちょっと面倒。
そんな時には下記のクラスを使って下さい。
UI無しで指定された動画の再生だけを行います。
再生・一時停止・シークなどはデレゲート、各メソッドを通じて外のUIを使うように作ってあります。

事前に AVFoundation.framework、CoreMedia.framework へのリンクを追加しておいて下さいね。

ヘッダー部から


#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@protocol MovieViewDelegate;

/**
 * 動画再生をView
 */
@interface MovieView : UIView
{

}

@property (nonatomic,assign) id<MovieViewDelegate> delegate;

//リピート再生するか
@property (nonatomic,assign) BOOL repeat;

//動画の長さ(秒数)
@property (nonatomic,readonly) Float64 movieDuration;

//再生を開始する
-(void)playMovie:(NSURL*)url;

//再生の一時停止
-(void)pauseMovie;

//再生開始
-(void)playMovie;

//再生位置を移動させる
-(void)seekToSeconds:(Float64)seconds;

//ムービーのクリア
- (void)clear;


@end


@protocol MovieViewDelegate <NSObject>

//動画の再生直前に呼び出される
-(void)movieView:(MovieView*)sender movieWillPlayItem:(AVPlayerItem*)playerItem duration:(Float64)duration;

//動画の再生中に一定の間隔で呼び出される
// time:再生位置(秒数)
// duration:動画全体の秒数
-(void)movieView:(MovieView*)sender moviePlayAtTime:(Float64)time duration:(Float64)duration;

@end


次に実装部はこんな感じ


#import "MovieView.h"


#define TIME_OVSERVER_INTERVAL  0.25f

@interface MovieView()
{   
    bool _is_playing;     //ムービーが再生中である事を示す
}

@property (nonatomic,retain) AVPlayerItem* playerItem;
@property (nonatomic,retain) AVPlayer*     player;
@property (nonatomic,assign) id  playTimeObserver;

@end

@implementation MovieView

#pragma mark -
#pragma mark class method

//自身のレイヤーを動画再生用レイヤーを返すようにオーバライド
+ (Class)layerClass
{
    return [AVPlayerLayer class];
}


#pragma mark -
#pragma mark instance management

-(void)dealloc
{
    _delegate = nil;
       
    [self clear];
   
    [_playerItem release];
    [_player release];

    [super dealloc];
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        _is_playing = false;
        _repeat = TRUE;
        self.backgroundColor = [UIColor clearColor];
    }
    return self;
}

#pragma mark property method

-(Float64)movieDuration
{
    if (self.playerItem)
    {
        Float64 duration = CMTimeGetSeconds( [self.player.currentItem duration] );
        return duration;
    } else {
        return 0;
    }
}



#pragma mark -
#pragma mark movie control

-(void)playMovie:(NSURL*)url
{
    //再生用アイテムを生成
    if (_playerItem)
    {
        //前回追加したムービー終了の通知を外す
        [[NSNotificationCenter defaultCenter] removeObserver:self
                                                        name:AVPlayerItemDidPlayToEndTimeNotification
                                                      object:self.playerItem];
       
        self.playerItem = nil;
    }
    self.playerItem = [[[AVPlayerItem alloc] initWithURL:url] autorelease];
   

    //Itemにムービー終了の通知を設定
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(playerDidPlayToEndTime:)
                                                 name:AVPlayerItemDidPlayToEndTimeNotification
                                               object:self.playerItem];
   
   
    Float64 movieDuration = CMTimeGetSeconds( self.playerItem.duration );
   
    if (self.player)
    {
               
        //アイテムの切り替え
        [self.player replaceCurrentItemWithPlayerItem:self.playerItem];
       
        [_delegate movieView:self movieWillPlayItem:self.playerItem duration:movieDuration];
       
    } else {

        //AVPlayerを生成
        self.player = [[[AVPlayer alloc] initWithPlayerItem:self.playerItem] autorelease];
        AVPlayerLayer* layer = ( AVPlayerLayer* )self.layer;
        layer.videoGravity = AVLayerVideoGravityResizeAspect;
        layer.player       = self.player;
       
        //delegate呼び出し
        [_delegate movieView:self movieWillPlayItem:self.playerItem duration:movieDuration];
       
        // 再生時間とシークバー位置を連動させるためのタイマーを設定
        __block MovieView* weakSelf = self;
        const CMTime intervaltime     = CMTimeMakeWithSeconds( TIME_OVSERVER_INTERVAL, NSEC_PER_SEC );
        self.playTimeObserver = [self.player addPeriodicTimeObserverForInterval:intervaltime
                                                                               queue:NULL
                                                                          usingBlock:^( CMTime time ) {
                                                                              //再生時になにか動作させるのであればここで。
                                                                              [weakSelf moviePlaying:time];
                                                                          }];
    }
   
    [self playMovie];
}




-(void)playMovie
{
    if (self.player)
    {
        [self.player play];
        _is_playing = true;
    }
}

-(void)pauseMovie
{   
    if (_is_playing)
    {
        [self.player pause];
        _is_playing = false;
    }
   
}

-(void)seekToSeconds:(Float64)seconds
{
    if (self.player)
    {
        [self.player seekToTime:CMTimeMakeWithSeconds(seconds, NSEC_PER_SEC )];
    }
}



//再生中のムービーを停止し、プレイヤーなどをクリアする
- (void)clear
{
    if (self.player)
    {
        [self.player removeTimeObserver:self.playTimeObserver];
        [self.player pause];
        [[NSNotificationCenter defaultCenter] removeObserver:self
          name:AVPlayerItemDidPlayToEndTimeNotification object:self.playerItem];
       
        AVPlayerLayer* layer = ( AVPlayerLayer* )self.layer;
        layer.player  = nil;
       
        self.player = nil;
        self.playerItem = nil;
        self.playTimeObserver = nil;       
    }
       
}

#pragma mark -
#pragma mark movie events

// TIME_OVSERVER_INTERVALで指定された間隔で実行される
-(void)moviePlaying:(CMTime)time
{
    Float64 duration = CMTimeGetSeconds( [self.player.currentItem duration] );
    Float64 time1    = CMTimeGetSeconds(time);
   
    if (_delegate){
   
        [_delegate movieView:self moviePlayAtTime:time1 duration:duration];
   
    }
   
}

// ムービー完了時に実行される
- (void) playerDidPlayToEndTime:(NSNotification*)notfication
{

    if (self.player)
    {
        if (self.repeat)
        {
            //リピート再生
            [self.player seekToTime:kCMTimeZero];
            [self.player play];
        }
    }
}


@end

使い方のサンプルは長くなったので次回へ持ち越し

卯辰山森林公園をおさんぽ(その2)

卯辰山森林公園をお散歩中にバラ以外にも色々撮りましたのでupしてみます。

可憐な花 チョウジソウかな?
涼しげで好きです
IMG_5152.jpg

ヒメカンゾウ かな?
ヒメカンゾウ

樹木公園にもありましたが、ヤマボウシ
ヤマボウシ
昔の忍者はこれを見て手裏剣を考えたに違いない

シロツメクサとミツバチ
ミツバチ
この写真はちょっとお気に入り

白鳥の池に住み着いているのかな?
とても仲良さそうでした。
つがいのカモ

卯辰山森林公園をおさんぽ(バラ園)

卯辰山森林公園をおさんぽです。
芝生の良い香りが気分をリフレッシュしてくれました。

ここにはバラ園があり、色んな種類のバラが咲いています
パシャパシャ撮ったものからいくつかピックアップしてご紹介

定番の赤以外にも
黄色
IMG_5143.jpg


IMG_5137.jpg
いろんなバラの中で白が一番好きかもしれません

オレンジ
IMG_5168.jpg
グラデーションが気に入りました

こんな模様のバラもあるんですね
IMG_5164.jpg

満開とまでは行きませんが、かなり咲いてきて見頃を迎えていますよ。

鶴来 ふれあい昆虫館

樹木公園いったついでに「ふれあい昆虫館」によって来ました。
私は年間パスポートを持っているので、行かないと損なのだw

ここにはチョウチョの温室があり、一年中色々な種類のチョウチョを見る事ができます。
以前にも紹介してますね。(記事

アサギマダラ(多分)
IMG_4964-2.jpg

アサギマダラ

オオゴマダラ(多分。。)

IMG_5056.jpg

スジグロカバマダラ(おそらく。。。)
IMG_4994.jpg

このこは分かりません。
IMG_5030.jpg

温室の中で涼しげに飛ぶ蝶は癒されますよ。
おすすめスポットです。

鶴来の樹木公園をぶらぶら

いいお天気が続いた1週間でしたね、雨も曇りもいいですが、やはり晴れが一番。

土曜日も朝からいいお天気でしたので、お気に入りの樹木公園に行って来ました。

コデマリ
コデマリ

これはなんだろ、手裏剣みたいですな
IMG_4911.jpg

カエデとキラキラ
IMG_4868.jpg

ハナグモ
IMG_4946.jpg
まだ赤ちゃんかな?

なんだろ、可愛い花ですね
IMG_4925.jpg

飛んで来たタンポポの種
IMG_4918.jpg

樹木公園の後に、初詣でもらった絵馬を返しに白山比咩神社に伺いました
IMG_4861.jpg
狛犬好きです。

UIScrollViewを一方向だけにしかスクロールを許さない方法

縦だけとか横だけのサンプルは良く見かけるのですが、例えば上スクロールだけを許して下にはスクロールさせない方法が見つからなかったので書いておきます。
(こんな制御に需要があるとはあまり思えませんが。。)

ポイント1 スクロール方向の判定

UIScrollViewDelegateのscrollViewWillBeginDragging、scrollViewDidScrollでスクロールの前回座標を保存し、
scrollViewDidScrollでスクロールされた後の座標と比較すればOK

ポイント2 スクロールのキャンセル

スクロールの方向を求め、許可する方向以外だった時はスクロールの位置をスクロールの開始座標に戻します。
また、座標を戻したとしても慣性スクロールが発生しますのでscrollViewWillBeginDeceleratingで慣性スクロールをキャンセルします。(以前の記事参照

実装は下記みたいな感じです

UIScrollViewDelegateを宣言に追加しておいて下さいね

//インスタンス変数としてどこかに宣言してください
CGPoint _scrollPrevPoint;  //スクロールの開始位置
BOOL _cancelDecelerating;  //慣性スクロールをキャンセルするフラグ    
int _scrolling_direction;  //0:未確定 1:上(offset.yが小さくなる) 2:下(offset.yが大きくなる)


#pragma mark - UIScrollViewDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{

    _scrolling_direction = 0;
    _cancelDecelerating = false;
    _scrollPrevPoint = [scrollView contentOffset];
}


-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    CGPoint currentPoint = [scrollView contentOffset];
   
    if (CGPointEqualToPoint(_scrollPrevPoint, currentPoint)){
        return;
    }
    else {
     //スクロール方向の判定
        _scrolling_direction = (_scrollPrevPoint.y < currentPoint.y) ? 2 : 1;
    }
   
    //下スクロールのキャンセル
    if (_scrolling_direction == 2)
    {    
        currentPoint.y = _scrollPrevPoint.y;
        [scrollView setContentOffset:currentPoint];
        _cancelDecelerating = true; //慣性スクロールを止めるためのフラグをセット
    }
}

- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
    //慣性スクロールを止める
    if (_cancelDecelerating){
        [scrollView setContentOffset:scrollView.contentOffset animated:NO];
    }
}

上記のような実装を行ったインスタンスをUIScrollViewのdelegateに指定してあげればOK

以上