안드로이드 학습(Kotlin)

20. SufaceView를 사용한 MediaPlayer 사용

리저브콜드브루 2025. 2. 7. 17:25
728x90
반응형

MediaPlayer

  • 로컬 또는 원격(인터넷) 미디어 파일 재생 가능
  • 오디오 및 비디오 재생 가능
  • 상태 변화(State) 관리 필요
  • SurfaceVIew, TextureView, VideoView를 사용하여 비디오 렌더링 가능
  • 재생, 일시 정지, 정지, 시크(Seek) 등의 기능 제공

 

MediaPlayer 상태 흐름

Idle → Initialized → Preparing → Prepared → Started → Paused → Stopped → Released
상태 설명
Idle MediaPlayer 객체가 생성된 초기 상태
Initialized setDataSource()로 재생할 파일 설정 후 상태
Preparing prepareAsync() 호출 후 미디어 준비 중
Prepared 미디어 준비 완료 (onPrepared() 호출됨)
Strated start() 호출 후 재생 상태
Paused pause() 호출 후 재생이 일시 중지됨
Stopped stop() 호출 후 다시 prepare() 필요
Released release() 호출 후 MediaPlayer 객체 해제

 

 


SurfaceView

안드로이드에서 화면에 그래픽(Rendering) 또는 미디어(Media) 데이터를 효율적으로 표시하기 위한 뷰(View)

동영상 재생(MediaPlayer), 게임 그래픽(Rendering), 카메라 미리 보기 등과 같이 빠른 렌더링이 필요한 경우 사용

 

특징

  • 별도의 Surface를 제공 → 일반적인 View와 다르게 별도의 Surface에서 동작하여 렌더링 성능 향상
  • UI Thread가 아닌 별도 Thread에서 렌더링 가능 → Canvas를 사용하여 직접 그리기 가능
  • MediaPlayer, OpenGL, Camera API와 함께 사용 가능
  • 빠른 화면 갱신이 필요할 때 적합 → 비디오 재생, 실시간 스트리밍, 게임 등에서 사용

SurfaceView vs TextureView

비교 항목 SurfaceView TextureView
렌더링 방식 별도의 Surface에서 렌더링 일반 뷰 계층에서 렌더링
성능 빠름 (전용 Surface 사용) 다소 느림 (뷰 계층 내에서 처리)
업데이트 방식 SurfaceRenderer를 사용하여 직접 렌더링 Canvas를 사용하여 뷰 내부에서 렌더링
투명도 지원 지원하지 않음 지원
뷰 애니메이션 직접 구현 필요 뷰 애니메이션 가능
주요 사용처 비디오 재생, 게임, 카메라 미리보기 부드러운 UI 애니메이션, OpenGL 렌더링

 

SurfaceView 기본 사용법

SurfaceView를 사용하려면 SurfaceHolder.Callback을 구현해야 한다.

SurfaceHolder.Callback는 SurfaceView가 생성되거나 변경될 때 호출되는 이벤트를 처리한다.

 

예제:SurfaceView로 화면 그리기

class CustomSurfaceView(context: Context, attrs: AttributeSet) : SurfaceView(context, attrs), SurfaceHolder.Callback {

    private val drawThread = DrawThread(holder)

    init {
        holder.addCallback(this) // SurfaceHolder 콜백 추가
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        drawThread.startDrawing()
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        drawThread.stopDrawing()
    }

    private class DrawThread(private val holder: SurfaceHolder) : Thread() {
        private var running = false

        fun startDrawing() {
            running = true
            start()
        }

        fun stopDrawing() {
            running = false
            join()
        }

        override fun run() {
            while (running) {
                val canvas = holder.lockCanvas()
                if (canvas != null) {
                    try {
                        canvas.drawColor(Color.BLACK) // 배경색 설정
                        val paint = Paint().apply { color = Color.RED }
                        canvas.drawCircle(200f, 200f, 100f, paint) // 원 그리기
                    } finally {
                        holder.unlockCanvasAndPost(canvas) // 화면 업데이트
                    }
                }
            }
        }
    }
}

 

  • SurfaceHolder.Callback을 구현하여 Surface의 상태 변화 감지
  • 별도의 스레드(DrawThread)에서 Canvas를 가져와 그림을 직접 그릴 수 있음
  • lockCanvas() → 그리기 작업 수행 → unlockCanvasAndPost() 호출하여 화면 갱신

SurfaceView를 활용한 MediaPlayer 동영상 재생

 

권한 추가

<uses-permission android:name="android.permission.INTERNET"/>
  • 원격 URL에서 동영상을 재생할 경우 필요

코드

class VideoPlayerActivity : AppCompatActivity(), SurfaceHolder.Callback {

    private lateinit var mediaPlayer: MediaPlayer
    private lateinit var surfaceView: SurfaceView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_video_player)

        surfaceView = findViewById(R.id.surfaceView)
        val holder = surfaceView.holder
        holder.addCallback(this)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        mediaPlayer = MediaPlayer()
        try {
            mediaPlayer.setDataSource("https://www.example.com/sample_video.mp4") // URL 또는 로컬 파일
            mediaPlayer.setDisplay(holder)
            mediaPlayer.prepareAsync()

            mediaPlayer.setOnPreparedListener {
                mediaPlayer.start() // 준비 완료 후 자동 재생
            }

            mediaPlayer.setOnCompletionListener {
                Toast.makeText(this, "재생 완료", Toast.LENGTH_SHORT).show()
            }

        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        mediaPlayer.release()
    }

    override fun onDestroy() {
        super.onDestroy()
        if (::mediaPlayer.isInitialized) {
            mediaPlayer.release()
        }
    }
}
  • SurfaceView를 사용하여 MediaPlayer가 영상을 렌더링
  • prepareAsync()로 비동기 방식으로 미디어 준비 후 onPrepared()에서 start() 호출
  • onCompletionListener에서 재생 완료 감지

추가 기능

재생 컨트롤 추가 (Play, Pause, Seek)

buttonPlay.setOnClickListener {
    if (!mediaPlayer.isPlaying) mediaPlayer.start()
}

buttonPause.setOnClickListener {
    if (mediaPlayer.isPlaying) mediaPlayer.pause()
}

buttonSeek.setOnClickListener {
    mediaPlayer.seekTo(30000) // 30초로 이동
}
  • start(), pause(), seekTo() 등을 활용하여 컨트롤 구현 가능

재생 시간 표시 (현재 시간, 총 길이)

val duration = mediaPlayer.duration / 1000 // 초 단위
val currentPosition = mediaPlayer.currentPosition / 1000
textView.text = "재생 시간: $currentPosition / $duration 초"
  • duration으로 총길이, currentPosition으로 현재 위치 조회 가능

비디오 재생 방법 비교 : MediaPlayer vs VideoView vs ExoPlayer

방식 특징 장점 단점 권장 사용처
MediaPlayer 저수준 API, SurfaceView 또는 TextureView 필요 세부 제어 가능, 다양한 포맷 지원 직접 UI 및 컨트롤러 구현 필요, 버퍼링 최적화 부족 로컬 파일 재생, 커스텀 플레이어 필요할 때
VideoView MediaPlayer를 내부적으로 사용 사용법 간단, 기본 컨트롤러 제공 (MediaController) 기능 제한(자막, DRM 지원 부족), 버퍼링 최적화 부족 간단한 로컬/온라인 동영상 재생
ExoPlayer Google에서 개발한 고급 미디어 플레이어 스트리밍 최적화, DASH/HLS 지원, 자막/DRM 지원 복잡한 API, 파일 크기 증가 (라이브러리 포함 필요) 네트워크 스트리밍, 고급 기능 필요할 때
  • 간단한 비디오 재생이 필요: VideoView
  • 로컬 비디오 + UI 커스텀 필요: MediaPlayer
  • 스트리밍 최적화, 자막/DRM 필요: ExoPlayer

 

728x90
반응형