본문 바로가기
아이폰 개발/ios 개념&튜토리얼

iOS AVPlayer 예제

by 인생여희 2021. 1. 26.

iOS AVPlayer 예제

 

순서

 

0.스토리보드에서 뷰 그리기

 

1. 라이브러리 import

 

2. 변수 선언

 

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

//비디오 뷰
@property (weak, nonatomic) IBOutlet UIView *videoView;

//AVPlayer 플레이어
@property (strong, nonatomic) AVPlayer *player;

//AVPlayerLayer
@property (strong, nonatomic) AVPlayerLayer *playerLayer;

//재생 여부 체크
@property (nonatomic, assign) BOOL isVideoPlaying;

//현재 시간 라벨
@property (weak, nonatomic) IBOutlet UILabel *currentTimeLabel;

//총길이 라벨
@property (weak, nonatomic) IBOutlet UILabel *durationLabel;

//타임 슬라이더
@property (weak, nonatomic) IBOutlet UISlider *timeSlider;

@end

 

3.AVPlayer 초기화 및 설정

 

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"viewDidLoad 진입");
    
    //단계1 - 파일 주소 생성
    NSURL *url = [[NSURL alloc]initWithString:@"https:/mp4?alt=media&token=dcef2268-bfb5-491e-9498-0a568833846a"];
    
    
    //단계2 - 파일 주소로 AVPlayer 초기화
    _player = [[AVPlayer alloc]initWithURL:url];
    
    
    //옵저버 등록 - duration key 값 변경 감지
    //observeValueForKeyPath를 사용하여 AVPlayer의 상태를 확인하는 방법!
    //이론적으로는 KVO 등록 이전에도 플레이어의 상태가 변경 될 수 있으며 이는 더 이상 KVO 콜백이 생성되지 않음을 의미.
    //KVO 등록을 수행 할 때 다음 옵션을 추가하는 것이 좋습니다 NSKeyValueObservingOptionInitial. 이렇게하면 초기 값에 대한 콜백도받을 수 있다.
    
    [_player.currentItem addObserver:self forKeyPath:@"duration" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:nil];
    
   
    //단계3 - 볼륨 설정
    [_player setVolume:0.0];
    
    
    //단계 4 - AVPlayerLayer 생성 및 비디오 뷰에 넣어주기
    _playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
    [_playerLayer setVideoGravity:kCAGravityResize];
    [_videoView.layer addSublayer: _playerLayer];
    
    _isVideoPlaying = NO;
    
}

 

4.뷰가 띄워질때 AVPlayerLayer 프레임 설정

 

- (void)viewWillAppear:(BOOL)animated{
    
    NSLog(@"viewWillAppear 진입");
    
    [self addTimeObserver];
    
}

//단계5 - 뷰가 띄워질때 AVPlayerLayer 프레임 설정
- (void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];
    _playerLayer.frame = _videoView.bounds;
    
}

 

6.플레이 버튼 클릭 ,정지버튼 클릭

 

//단계6 - 버튼 셋팅
//플레이 버튼 클릭 / 정지버튼 클릭
- (IBAction)playPressed:(UIButton *)sender {
    
    //재생중에 클릭했으면
    if (_isVideoPlaying) {
        
        //멈춤
        [_player pause];
        
        //타이틀 변경
        [sender setTitle:@"Play" forState:UIControlStateNormal];
        
        //값 변경
         _isVideoPlaying = NO;
        
    }else{
        
        //멈춰있을 때 클릭 시 - 재생
        [_player play];
        
        //타이틀 변경
        [sender setTitle:@"Pause" forState:UIControlStateNormal];
        
         _isVideoPlaying = YES;
        
    }
}

 

7.앞으로 버튼 클릭 함수

 

//단계 7 - 이후 버튼 클릭 설정
//이후 5초
- (IBAction)forwardPressed:(UIButton *)sender {

    //전체 길이 cmtime
    CMTime duration =  _player.currentItem.duration;
    
    //현재 시간초
    Float64 currentTime =  CMTimeGetSeconds([_player currentTime]);
    
    //새로운 시간 구하기 : 현재시간 + 5초
    Float64 newTime = currentTime + 5.0;
    
    //전체시간 - 5초 보다 새로운 시간이 더 작다면
    //예컨데, 전체시간(60초) - 5초 = 55초 에서 새로운 시간이 56초면 5초 뒤로 넘어갈 수 없다.
    if (newTime < (CMTimeGetSeconds(duration) - 5.0)) {
        
        
        CMTime time =  CMTimeMake((newTime*1000), 1000);
      
        //새로운 시간 위치로 이동
        [_player seekToTime:time];
    }
}

 

8.뒤로가기 버튼 클릭 함수

 

//단계8 - 이전 버튼 클릭 설정
//이전 5초
- (IBAction)backwordPressed:(UIButton *)sender {
    
    //현재 시간 초
    Float64 currentTime =  CMTimeGetSeconds([_player currentTime]);
    
    //새로운 시간 초 : 현재시간 초 - 5초
    Float64 newTime = currentTime - 5.0;
    
    //새로운 시간이 0보다 작으면 0
    if (newTime < 0) {
        newTime = 0;
    }
    
    CMTime time =  CMTimeMake((newTime*1000), 1000);
    [_player seekToTime:time];
    
    
}

 

9.슬라이더 변경 감지 함수

 

//단계9 - 슬라이더 변경 설정
//슬라이더 변경
- (IBAction)sliderValueChaged:(UISlider *)sender {
    
    //sender.value에는 현재 슬라이더의 값이 들어가있다.
    
    CMTime time =  CMTimeMake((UInt64)sender.value * 1000, 1000);
    [_player seekToTime:time];
    
}

 

10.옵저버 오버라이드 메소드 작성 - KVO

 

//단계10 - 옵저버 오버라이드 메소드 작성 - KVO
//currentItem의 전체 재생 시간 구하기
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    NSLog(@"observeValueForKeyPath 진입");
    
    //등록한 옵저버의 key 가 'duration' 이면
    if ([keyPath isEqualToString:@"duration"]) {
                
        //현재 재생될 자산이 준비가 되었다면
            NSLog(@"observeValueForKeyPath ready!");
                
                //현재 재생자산의 전체길이 cmTime
                CMTime durationCmTime = _player.currentItem.duration;
            
                 /*
                        CMTime이 유효하지 않거나 무기한이면 NaN이 반환됩니다.
                        CMTime이 무한대이면 +/- 무한대가 반환됩니다.
                        CMTime이 숫자이면 epoch는 무시되고 time.value / time.timescale이 반환됩니다.
                        나누기는 Float64에서 수행되므로 반환 된 결과에서 분수가 손실되지 않습니다.
                     */
            
                 //현재 재생자산의 전체길이 초
                Float64 duration = CMTimeGetSeconds(durationCmTime);
                
                //전체 길이가 0 보다 크면 전체 재생 시간 text로 설정
                if (duration > 0.0) {
                    //float 형을 시: 분 : 초 문자열로 변경
                    self.durationLabel.text = [self getTimeString:duration];
                }
            
      
    }
}

 

11.float 형을 시: 분 : 초 문자열로 변경 하는 함수 작성

 

//단계11 - float 형을 시: 분 : 초 문자열로 변경 하는 함수 작성
-(NSString *) getTimeString:(Float64) timeTotalSecond{
    
    NSLog(@"getTimeString 진입");
    NSLog(@"변경할 초 timeTotalSecond time : %f" , timeTotalSecond);
    
    //시, 분, 초 계산
    int hours =  (unsigned int)(timeTotalSecond / 3600);
    int minutes = (unsigned int)(timeTotalSecond / 60) % 60;
    int secondes = (unsigned int)timeTotalSecond % 60;
    
    //시간이 존재한다면
    if (hours > 0) {
        NSString *result = [[NSString alloc]initWithFormat:@"%i:%02i:%02i" , hours, minutes, secondes];
        return result;
    }else{
        NSString *result = [[NSString alloc]initWithFormat:@"%02i:%02i" , minutes, secondes];
        return result;
    }
}

 

12.시간변경 감지 옵저버 작성

 

//단계12 시간변경 감지 옵저버 작성
-(void) addTimeObserver{

    NSLog(@"addTimeObserver 진입");

    CMTime interval = CMTimeMakeWithSeconds(0.5, NSEC_PER_SEC);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    //여기서self를 카피
    //ARC로 된 프로젝트에서 블럭코딩을 할 경우 블럭 밖에 선언된 변수가 블럭안에서 값이 변할때 위와같은 에러가 발생한다고 한다.
    //__block ViewController *blockself = self;
    __weak typeof(self) weakSelf = self;
    
    [_player addPeriodicTimeObserverForInterval:interval queue:mainQueue usingBlock:^(CMTime time) {
        
        AVPlayerItem *curentItem =  weakSelf.player.currentItem;
        
        NSLog(@"AVPlayerItem 상태값 : %ld" , (long)[curentItem status]);
        
        if([curentItem status] == 1){
            
            NSLog(@"AVPlay ready!!");
            
            Float64 checkTIme = CMTimeGetSeconds(curentItem.duration);
            
            NSLog(@"checkTIme : %f" , checkTIme);
        
            NSLog(@"maximumValue : %f" , CMTimeGetSeconds(curentItem.duration));
            
            if (checkTIme) {
                NSLog(@"타임슬라이스 값 셋팅 완료 !");
                weakSelf.timeSlider.maximumValue = CMTimeGetSeconds(curentItem.duration); //전체 길이
                weakSelf.timeSlider.minimumValue = 0;
                weakSelf.timeSlider.value = CMTimeGetSeconds(curentItem.currentTime);
                weakSelf.currentTimeLabel.text = [weakSelf getTimeString:CMTimeGetSeconds(curentItem.currentTime)];
            }
            
        }else{
            NSLog(@"AVPlay not ready!");
        }
            
    }];
    
}

@end

 

 

참고

 

KVO 참고 : https://mrgamza.tistory.com/448

CMTime 참고 : https://medium.com/@ppth0608/cmtime%EC%97%90-%EB%8C%80%ED%95%B4-%ED%8C%8C%ED%95%B4%EC%B3%90%EB%B3%B4%EC%9E%90-965f343ca7f1

 

Avfoundation / avkit 참고

https://baked-corn.tistory.com/118

https://wnstkdyu.github.io/2018/05/03/avfoundationprogrammingguide

https://hyerios.tistory.com/179

https://devhkh.tistory.com/18

https://hyunsikwon.github.io/ios/iOS-AVFoundation