안드로이드 앱 개발 공부/자꾸 까먹어서 적어두는 구현방법

[Android] 버튼+ 홈 키 사용해서 비디오 pip 구현 _ kotlin 코틀린

플래시🦥 2023. 10. 16.
반응형

ott앱을 사용하다 보면 PIP기능으로 자주 사용한다. 그래서 항상 사용하면서 어떻게 구현하는 것인지 궁금했다. 

그래서 한번 해봤다. 

 

내가 구현해 보길 원하는 방법은 재생되는 영상을 보다가 PIP를 사용할 수 있는 것이다. 

 

1. 영상이 재생 중이어야 한다.

2-1. 티빙 앱처럼 버튼을 눌러서 pip모드로 진입한다.

2-2. 넷플릭스나 웨이브 앱처럼 홈키를 눌러서 pip모드로 진입한다. 

 

이 세가지를 만족할 수 있도록 만들고 싶었다. 

 

1. 영상 재생 

링크를 타고 영상을 가지고 올 수 있는 방법도 있지만 저장되어 있는 비디오를 가져오는 방법을 선택했다. 

그러려면 처음으로는 영상을 가져와야 했다. 

픽사베이에서 무료 영상을 다운을 받았다. 

이 영상을 보여줄 수 있는 비디오뷰를 xml에 넣어주는 것이 가장 먼저 해야 할 일이다.

나는 영상 재생 버튼도 추가해 주었다. 

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/startBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="play"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/videoView" />

원하는 위치에 비디오뷰를 넣어준다. 

그리고 Activity()에서 비디오가 재생될 수 있는 코드를 넣어준다,

binding.startBtn.setOnClickListener {
            binding.videoView?.run {
                setOnCompletionListener { it.start() }
                val uri = Uri.parse("android.resource://" + packageName + "/" + R.raw.kangaroo_1080p_sample)
                setVideoURI(uri)
                start()
            }
        }

영상이 바로 시작되지 않고 시작 버튼을 누르면 영상이 시작될 수 있도록 작성해 주었다. 

 

**동영상 가지고 오는 방법

res를 우클릭해서 android resource directory파일을 누르고 raw를 찾아 추가해 준다. 

그리고 raw폴더에 원하는 영상을 넣으면 된다. 

영상이 추가됨.

 

2. 티빙처럼 버튼 눌러 pip 

버튼을 눌러 pip 하기 위해서 가장 필요한 것은 androidManifest에 아래 태그를 넣어주는 것이다. 

  <activity
            android:name=".PipActivity"
            android:exported="false"
            android:theme="@style/AppTheme.NoActionBar"
            android:resizeableActivity="true"
            android:supportsPictureInPicture="true"
            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>

이게 있어야 pip를 사용할 수 있다. 

여기서 

android:resizeableActivity="true" 는 앱이 멀티 윈도우 모드를 지원하는지 여부를 지정한다. 
android:supportsPictureInPicture="true"는 활동이 PIP 모드 표시를 지원하는지를 지정하고, 
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>는

상태유지를 위한 부분이다. 위 부분을 원하는 액티비티에 넣어준다. **해당 액티비티에 NoActionBar테마가 지정되어 있는데 해당 액티비티에서 액션바가 있는 상태라면 pip를 했을 때도 pip화면에 액션바가 보이게 된다. 이를 원치 않는다면 해당 액티비티의 액션바를 없애주어야 한다. 

 

그다음에는 버튼을 누르면 pip가 되도록 해주는데, 우선 버튼을 추가해 주었다. 

<Button
        android:id="@+id/pipBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="PIP"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/videoView" />

그리고 나서 해당 버튼을 누르면 pip 모드로 진입할 수 있도록 하는 함수를 작성해 주었다. 

rivate val mPipBuilder = PictureInPictureParams.Builder()

private fun startPip() {
        if(binding.videoView.isPlaying) {
            mPipBuilder.setAspectRatio(Rational(binding.videoView.width, binding.videoView.height))
            enterPictureInPictureMode(mPipBuilder.build())
        }
    }

여기서 binding.videiView.isPlaying의 if문이 없다면 영상이 재생되지 않더라도 pip가 실행되기 때문에 추가해 주었다. 

그리고 pip실행을 위해 버튼 리스너에서 startPip()하면 pip가 된다. 

버튼눌러 pip

 

3. 넷플릭스나 웨이브처럼 홈키 눌러 pip

처음 pip를 구현하기 위해 찾아 방법을 찾아볼 때 버튼을 눌러서 하는 방법만 나와서 어떻게 해야 할지 당황스러웠다. 

그래서 처음에는 앱이 포커스를 잃는 상태가 되면 pip를 실행하도록 해야 하나 하는 생각으로 코드를 작성했지만 그냥 꺼지고 로그에 오류만 떴다. 그래서 곰곰이 생각해 보니 지금까지 여러 프로젝트를 하면서 사용했던 키보드 이벤트가 생각이 났다. 혹시 홈키도 특정 방식이 있는 것은 아닐까 하는 생각이 들어 찾아보았고 찾을 수 있었다!

 

홈키를 눌렀을 때 어떤 작업이 가능하도록 하고 싶다면 onUserLeaveHint를 오버라이드 헤서 사용하면 된다. 

해당 함수에서 아까 만든 pip실행 함수를 호출하면 완성이다. 

 override fun onUserLeaveHint() {
        super.onUserLeaveHint()
        startPip()
    }

홈키 눌러 pip

 

*전체 코드

1. xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PipActivity">

    <VideoView
        android:id="@+id/videoView"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/startBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="play"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/videoView" />

    <Button
        android:id="@+id/pipBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="PIP"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/videoView" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

2.Activity()

import android.app.PictureInPictureParams
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Rational
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import 패키지.databinding.ActivityPipBinding


@RequiresApi(Build.VERSION_CODES.O)
class PipActivity : AppCompatActivity() {

    private val binding by lazy { ActivityPipBinding.inflate(layoutInflater) }
    private val mPipBuilder = PictureInPictureParams.Builder()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.startBtn.setOnClickListener {
            binding.videoView?.run {
                setOnCompletionListener { it.start() }
                val uri = Uri.parse("android.resource://" + packageName + "/" + R.raw.kangaroo_1080p_sample)
                setVideoURI(uri)
                start()
            }
        }
        binding.pipBtn.setOnClickListener {
            startPip()
        }

    }

    private fun startPip() {
        if(binding.videoView.isPlaying) {
            mPipBuilder.setAspectRatio(Rational(binding.videoView.width, binding.videoView.height))
            enterPictureInPictureMode(mPipBuilder.build())
        }
    }

    override fun onUserLeaveHint() {
        super.onUserLeaveHint()
        startPip()
    }
}

3.Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        ...
        >
        <activity
            android:name=".PipActivity"
            android:exported="false"
            android:theme="@style/AppTheme.NoActionBar"
            android:resizeableActivity="true"
            android:supportsPictureInPicture="true"
            android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
       ...
    </application>

</manifest>
728x90
반응형

댓글