[안드로이드] - [안드로이드] ExoPlayer (1) - 이게 뭐야 & 설정하기 & 시작하기
[안드로이드] ExoPlayer (1) - 이게 뭐야 & 설정하기 & 시작하기
회사에 와서 미디어 관련 라이브러리를 처음 다뤄보게 되어서 한번 정리를 해보고자 합니다. ExoPlayer 란? ExoPlayer는 SM엔터ㅌ 구글에서 만든 미디어 재생 라이브러리입니다. 여러 종류의 미디어
dev-genue.tistory.com
에 이어서 이번 글에서는 ExoPlayer를 활용해 보겠습니다.
이전 글에서 한 것처럼, 유튜브 영상을 그냥 Url에 넣어보니 작동하지가 않았습니다.
String sample = "https://www.youtube.com/watch?v=AOMQQGNi2-U";
MediaItem mediaItem = MediaItem.fromUri(sample);
player.setMediaItem(mediaItem);
player.seekTo(currentWindow, playBackPosition);
player.prepare();
player.play();
E/ExoPlayerImplInternal: Playback error
com.google.android.exoplayer2.ExoPlaybackException: Source error
Caused by: com.google.android.exoplayer2.source.UnrecognizedInputFormatException: None of the available extractors (FlvExtractor, FlacExtractor, WavExtractor, FragmentedMp4Extractor, Mp4Extractor, AmrExtractor, PsExtractor, OggExtractor, TsExtractor, MatroskaExtractor, AdtsExtractor, Ac3Extractor, Ac4Extractor, Mp3Extractor, JpegExtractor) could read the stream.
오류를 보면 stream을 읽어오는 Extractor가 알맞지 않다고 되어있습니다.
그래서 찾아보니 ExoPlayer samples에서 DASH URL을 이용하여 유튜브 영상을 재생시키는 것을 확인할 수 있었습니다.
그럼 이 DASH URL은 어떻게 얻어오느냐? 이것도 검색을 해보니 원하는 영상 파일을 다운로드 후에 파일에서 dashmpd으로 시작하고 첫 &으로 끝나는 부분을 가져오라고 합니다.
그치만, 하다보니 머리가 깨질 것 같아서 그냥 포기하고 어느 선생님이 만드신 youtubeExtractor 라이브러리를 가져다 쓰기로 했습니다. 세상엔 진짜 똑똑이들이 많은 것 같아요

깃헙 주소 : https://github.com/HaarigerHarald/android-youtubeExtractor
implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
요걸 추가해주고, 바로 사용해보겠습니다.
String sample = "https://www.youtube.com/watch?v=AOMQQGNi2-U";
public void initPlayer(){
player = new SimpleExoPlayer.Builder(this).build();
playerView.setPlayer(player);
new YouTubeExtractor(this) {
@Override
protected void onExtractionComplete(SparseArray<YtFile> ytFiles, VideoMeta videoMeta) {
if (ytFiles != null) {
int videoTag = 135;//Video tag for 1080p MP4(debug ytFiles to get tag)
int audioTag = 140;//Audio tag for m4a
int itag = 22;
MediaSource item = new ProgressiveMediaSource.Factory(new DefaultHttpDataSource.Factory())
.createMediaSource(MediaItem.fromUri(ytFiles.get(itag).getUrl()));
player.setMediaSource(item);
player.prepare();
//player.setPlayWhenReady(true);//준비되면 자동으로 재생됨
player.play();
}
}
}.extract(sample, true, true);;
}
extract로 원하는 유튜브 영상 주소에서 영상을 가져오도록 하고, extract가 끝나면 onExtractionComplete에서 처리를 해줍니다.
변수 itag는 사용할 미디어의 종류에 따라 구분됩니다. 태그 22는 영상과 음악을 동시에 사용하기 위한 태그입니다. 영상에 따라 알맞는 tag가 다르기 때문에 만일 안맞으면
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String at.huber.youtubeExtractor.YtFile.getUrl()' on a null object reference
오류가 발생합니다. 그러면 try..catch 문을 사용하여 오류 발생 시 tag를 증가시켜서 맞는 태그를 찾으면 됩니다.
int itag = 17;
while(true){
try{
MediaSource item = new ProgressiveMediaSource.Factory(new DefaultHttpDataSource.Factory())
.createMediaSource(MediaItem.fromUri(ytFiles.get(itag).getUrl()));
player.setMediaSource(item);
break;
} catch (NullPointerException e){
e.printStackTrace();
itag++;
}
}
대신 이러면 최적의 태그보단 최저의 태그를 사용할 수도 있습니다.
태그의 종류를 보면 ( 여기서 확인 가능 )
FORMAT_MAP.put(17, new Format(17, "3gp", 144, Format.VCodec.MPEG4, Format.ACodec.AAC, 24, false)); FORMAT_MAP.put(36, new Format(36, "3gp", 240, Format.VCodec.MPEG4, Format.ACodec.AAC, 32, false)); FORMAT_MAP.put(5, new Format(5, "flv", 240, Format.VCodec.H263, Format.ACodec.MP3, 64, false)); FORMAT_MAP.put(43, new Format(43, "webm", 360, Format.VCodec.VP8, Format.ACodec.VORBIS, 128, false)); FORMAT_MAP.put(18, new Format(18, "mp4", 360, Format.VCodec.H264, Format.ACodec.AAC, 96, false)); FORMAT_MAP.put(22, new Format(22, "mp4", 720, Format.VCodec.H264, Format.ACodec.AAC, 192, false));
만약 필요한 태그가 18, 22 인데 번호를 정확히 몰라 5부터 찾는다고 하면 18에서 멈추겠죠? 더 높은 성능의 22말고 18을 찾는 경우도 생길 수도 있으니 참고해야합니다.
위에 태그는 영상과 음악을 한번에 가져온다면, 필요시 영상만 틀던지, 음악만 필요한 경우가 있습니다. 그럴 경우에는
new YouTubeExtractor(this) {
@Override
protected void onExtractionComplete(SparseArray<YtFile> ytFiles, VideoMeta videoMeta) {
if (ytFiles != null) {
int videoTag = 133;//Video tag for 1080p MP4(debug ytFiles to get tag)
//비디오만 재생
while(true){
try{
MediaSource videoSource = new ProgressiveMediaSource.Factory(new DefaultHttpDataSource.Factory())
.createMediaSource(MediaItem.fromUri(ytFiles.get(videoTag).getUrl()));
Log.d(">>>>", "videoTag " + videoTag);
player.setMediaSource(videoSource);
break;
}catch (NullPointerException e){
videoTag++;
}
}
player.prepare();
//player.setPlayWhenReady(true);//준비되면 자동으로 재생됨
player.play();
}
}
}.extract(sample, true, true);
이러면 영상만 재생됩니다. 만일 음성만 하고싶다면 음성태그를 가져다가 쓰시면 됩니다.
재생이 되었으면, 이제 컨트롤을 해봐야할 차례입니다. 몇가지만 소개하고 나머지는 공식 문서를 확인하시기 바랍니다.
- 리스너 추가
new YouTubeExtractor(this) {
@Override
protected void onExtractionComplete(SparseArray<YtFile> ytFiles, VideoMeta videoMeta) {
if (ytFiles != null) {
player.addListener(new Player.Listener() {
@Override
public void onPlaybackStateChanged(int state) {
switch (state) {
case Player.STATE_IDLE:
Log.d(">>>>", "onIsPlayingChanged STATE_IDLE");
//플레이어 멈춤
//플레이 실패
break;
case Player.STATE_BUFFERING:
Log.d(">>>>", "onIsPlayingChanged STATE_BUFFERING");
//버퍼링 중
break;
case Player.STATE_READY:
Log.d(">>>>", "onIsPlayingChanged STATE_READY");
//재생 준비 완료
//만일 player.setPlayWhenReady(true)이면 플레이됨
break;
case Player.STATE_ENDED:
Log.d(">>>>", "onIsPlayingChanged STATE_ENDED");
//영상 끝까지 재생했으면
break;
}
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
if (isPlaying) {
//재생하면
} else {
//재생 멈추면
}
}
});
}
}
}.extract(sample, true, true);
각 상태에 대한 설명은 제가 이해하기 편한대로 적은 것이라 공식 문서에서 확인해보시기 바랍니다.
- 플레이 상태 백업
홈키를 누르거나 화면이 꺼질 시 현재 재생되던 위치에서 재시작하기 위한 작업입니다.
먼저 현재 플레이 상태를 저장하는 함수를 만듭니다.
boolean playWhenReady = false;//player.setPlayWhenReady()의 인자로 쓰일 녀석
long playBackPosition = 0;
int currentWindow = 0;
private void releasePlayer() {
if (player != null) {
playWhenReady = player.getPlayWhenReady();//마지막 상태 확인
playBackPosition = player.getCurrentPosition();//마지막 동영상 위치 확인
currentWindow = player.getCurrentWindowIndex();//현재 재생중인 영상 정보인듯?
player.release();
player = null;
}
}
ExoPlayer 의 window 란 녀석의 역할이 뭔지는 모르겠지만, 대충 현재 영상의 정보를 가지고 있는 걸로 보입니다. 나중에 공부해보고 추가해두겠습니다. 암튼 이 함수를 이제 onStop 또는 onPause 에 넣으면 됩니다.
@Override
protected void onPause() {
super.onPause();
releasePlayer();
}
자원 해제를 하기위해서는 자원이 있는 상태여야겠죠? 위에서 만든 initPlayer를 onCreate에 넣고 onResume에서 자원 해제된 경우를 처리해줍니다.
@Override
protected void onResume() {
super.onResume();
if (player == null) {
initPlayer();
}
}
그리고 이제 백업둔 마지막 상태를 사용하겠습니다.
public void initPlayer(){
.
.
new YouTubeExtractor(this) {
@Override
protected void onExtractionComplete(SparseArray<YtFile> ytFiles, VideoMeta videoMeta) {
if (ytFiles != null) {
.
.
player.prepare();
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playBackPosition);
}
}
}.extract(sample, true, true);;
}
여기까지 아주 간단한 ExoPlayer 의 기능을 맛 봤습니다. 딱 제가 필요한 정도만 확인한거라 더 많은 기능들이 숨어있지만, 사용은 못해봤습니다. 다음번 글에는 유튜브의 재생목록에서 동영상들을 추출해서 작동시키는 법을 알아보도록 하겠습니다.
'안드로이드' 카테고리의 다른 글
[안드로이드] Custom View 커스텀 뷰 (3) - 애니메이션 추가 및 뻘 짓 (0) | 2021.07.20 |
---|---|
[안드로이드] Custom View 커스텀 뷰 (2) - 화면 터치 시 생성 (0) | 2021.07.16 |
[안드로이드] Custom View 커스텀 뷰 (1) - 만들기 (0) | 2021.07.15 |
[안드로이드] 네비게이션바 백키 기능 변경 (0) | 2021.07.08 |
[안드로이드] ExoPlayer (1) - 이게 뭐야 & 설정하기 & 시작하기 (0) | 2021.07.02 |