Slide 1

Slide 1 text

compose-video 오픈소스 라이브러리 개발기 (배포까지) 이상훈 / at Studio

Slide 2

Slide 2 text

이상훈 (Dora Lee) at Studio Product Engineer @dsa28s

Slide 3

Slide 3 text

INDEX 질문이 있다면 여기서! (slido) ❏ 1. compose-video 라이브러리 소개 ❏ 2. compose-video 탄생 비화 ~ 개발 여정 ❏ 3. 라이브러리 배포하기 (compose-video 라이브러리로 예시) ❏ Step 0. JavaDoc 호환 가능 주석 달기 ❏ Step 1. 라이브러리 샘플 앱 만들기 ❏ Step 2. Maven Central Portal 가입 + Namespace 등록 ❏ Step 3. 라이브러리 서명하기 ❏ Step 4. 배포 스크립트 작성 + Maven Central 에 배포하기

Slide 4

Slide 4 text

compose-video 라이브러리 소개 1.

Slide 5

Slide 5 text

compose-video ❏ https://github.com/dsa28s/compose-video ❏ Jetpack Compose 환경에서 ExoPlayer 를 원활히 사용하고 TextureView(PlayerView)를 사용하기 위해 탄생한 라이브러리 ❏ 라이브러리 내부에 ExoPlayer Player 인스턴스를 Lifecycle 에 알아서 핸들링하며, 다양한 기능들 지원

Slide 6

Slide 6 text

compose-video 탄생 비화 ~ 개발 여정 2.

Slide 7

Slide 7 text

compose-video 왜 만들게 되었나요? 서비스 하는 앱이 100% Jetpack Compose로 작성되어 있었음

Slide 8

Slide 8 text

compose-video 왜 만들게 되었나요? 유저가 비디오를 스트리밍 할 수 있어야 하고 재생, 일시정지, 전체화면 다 되어야 하는 기능을 만들기로 함

Slide 9

Slide 9 text

compose-video 왜 만들게 되었나요? 설마 Compose 환경에 ExoPlayer 컴포넌트가 없겠어...?

Slide 10

Slide 10 text

compose-video 왜 만들게 되었나요? 음.. 공식은 없네? 서드파티 라이브러리를 찾아볼까?

Slide 11

Slide 11 text

compose-video 왜 만들게 되었나요? .......................? 없다 비상비상! 해서 만들게 되었습니다

Slide 12

Slide 12 text

compose-video 요구사항 #1 ❏ Jetpack Compose 환경에서 ExoPlayer 기반 비디오 플레이어 UI 컴포넌트 사용하기 + 영상 스트리밍 가능해야 함

Slide 13

Slide 13 text

VideoPlayer( mediaItems = ..., handleLifecycle = true, autoPlay = true, usePlayerController = true, handleAudioFocus = true, controllerConfig = VideoPlayerControllerConfig( showSpeedAndPitchOverlay = false, showSubtitleButton = false, showCurrentTimeAndTotalTime = true, showBufferingProgress = false, showForwardIncrementButton = true, showBackwardIncrementButton = true, showBackTrackButton = true, ... compose-video Compose Component

Slide 14

Slide 14 text

VideoPlayer( ..., showRepeatModeButton = true, controllerShowTimeMilliSeconds = 5_000, controllerAutoShow = true, ), volume = 0.5f, // volume 0.0f to 1.0f repeatMode = RepeatMode.NONE, // or RepeatMode.ALL, RepeatMode.ONE modifier = ... ) compose-video Compose Component

Slide 15

Slide 15 text

compose-video 요구사항 #2 ❏ VideoPlayer 컴포넌트를 사용하면 알아서 전체화면 처리

Slide 16

Slide 16 text

compose-video 요구사항 #2 ❏ VideoPlayer 컴포넌트를 사용하면 알아서 전체화면 처리 ❏ ExoPlayer.PlayerView 인스턴스 내부에 사용자가 전체화면 버튼을 눌렀을 때의 핸들러 (setControllerOnFullScreenModeChangedListener) 가 존재함

Slide 17

Slide 17 text

compose-video 요구사항 #2 ❏ VideoPlayer 컴포넌트를 사용하면 알아서 전체화면 처리 ❏ ExoPlayer.PlayerView 인스턴스 내부에 사용자가 전체화면 버튼을 눌렀을 때의 핸들러 (setControllerOnFullScreenModeChangedListener) 가 존재함 ❏ 전체화면 됐을 때, ExoPlayer가 전체화면을 알아서 핸들링 해주지 않음 → 전체화면 됐을 때 Compose Dialog를 현재 액티비티에 띄워서 전체화면 뷰 또한 Compose로 구현

Slide 18

Slide 18 text

해치웠나?

Slide 19

Slide 19 text

해치웠나? QA 시작 상훈님~~! 영상은 잘 나오는데 다른 앱이랑 소리가 겹쳐요 영상 처음에 틀 때는 자동 재생 되어야 해요

Slide 20

Slide 20 text

영상은 잘 나오는데 다른 앱이랑 소리가 겹쳐요 compose-video 요구사항 #3 VideoPlayer( ..., handleAudioFocus = true, ..., ) VideoPlayer 컴포넌트에서 handleAudioFocus 라는 파라미터를 받게 하고,

Slide 21

Slide 21 text

val player = remember(handleAudioFocus) { val httpDataSourceFactory = DefaultHttpDataSource.Factory() ExoPlayer.Builder(context) ... .setAudioAttributes( AudioAttributes.Builder() .setContentType(AUDIO_CONTENT_TYPE_MOVIE) .setUsage(C.USAGE_MEDIA) .build(), handleAudioFocus, // 아까 VideoPlayer 컴포넌트에서 받았던 props ) ... }

Slide 22

Slide 22 text

영상 처음에 틀 때는 자동 재생 되어야 해요 compose-video 요구사항 #4 VideoPlayer( ..., autoPlay = true, ..., ) VideoPlayer 컴포넌트에서 autoPlay 라는 파라미터를 받게 하고, Player remember 블록에서 autoPlay 함수에 따라 player.play() 함수 호출

Slide 23

Slide 23 text

해치웠나? QA 해치웠나?

Slide 24

Slide 24 text

해치웠나? QA 해치웠나? 찐막 상훈님~~! 사용자가 얼마나 봤는지 데이터 로깅 좀 심어주세요~

Slide 25

Slide 25 text

데이터 로깅이 됐으면 좋겠어요 compose-video 요구사항 #5 VideoPlayer( ..., onCurrentTimeChanged = { // long type, current player time (ms) Log.e("CurrentTime", it.toString()) }, ..., )

Slide 26

Slide 26 text

VideoPlayer( ..., onCurrentTimeChanged = { // long type, current player time (ms) Log.e("CurrentTime", it.toString()) }, ..., ) 영상을 어디까지 봤는지는 데이터 로깅 사항에 포함되어 있었기 때문에, onCurrentTimeChanged 라는 콜백을 만들어서 1초에 한번씩 현재의 위치를 밀리초 (ms) 단위로 리턴하게 함

Slide 27

Slide 27 text

해치웠나? QA 해치웠나? 찐막 + 또다른 고민 완료.. 근데 데이터 로깅은 아마 더 늘어날 수 있으니까 그냥 ExoPlayer에서 모든 정보를 줄 수 있게 하면 어떨까?

Slide 28

Slide 28 text

compose-video 확장성 고려하기 VideoPlayer( ..., playerInstance = { // ExoPlayer instance (Experimental) addAnalyticsListener( object : AnalyticsListener { // player logger } ) }, ..., )

Slide 29

Slide 29 text

VideoPlayer( ..., playerInstance = { // ExoPlayer instance (Experimental) addAnalyticsListener( object : AnalyticsListener { // player logger } ) }, ..., ) ExoPlayer에서 플레이어 자체의 모든 종류의 이벤트를 트래킹 할 수 있게 차라리 내부적으로 초기화 된 player 인스턴스를 사용할 수 있도록 제공

Slide 30

Slide 30 text

해치웠나? QA 해치웠나? 찐막 + 또다른 고민 + 재밌는데? 하다보니 뭔가 재밌어짐 회사에서 올리는 콘텐츠가 조금씩 길어지고.. 전체화면으로만 보기에는 다른 앱 사용을 못해서 너무 번거로웠음 → PIP 만들어볼까?

Slide 31

Slide 31 text

compose-video PIP 만들기 ❏ VideoPlayer 컴포넌트를 사용하고 백그라운드로 옮기면 PIP가 호출되게 할 수는 없을까?

Slide 32

Slide 32 text

compose-video PIP 만들기 ❏ VideoPlayer 컴포넌트를 사용하고 백그라운드로 옮기면 PIP가 호출되게 할 수는 없을까? ❏ ComposeView로 감싸진 PlayerView를 토대로 Android O (8.0) 부터 공식 지원하는 PIP를 구현함

Slide 33

Slide 33 text

compose-video PIP 만들기 ❏ VideoPlayer 컴포넌트를 사용하고 백그라운드로 옮기면 PIP가 호출되게 할 수는 없을까? ❏ ComposeView로 감싸진 PlayerView를 토대로 Android O (8.0) 부터 공식 지원하는 PIP를 구현함 ❏ 자세한 사항은 compose-video 라이브러리 내에 pip 패키지 / PictureInPicture.kt 파일과 샘플 앱 참조

Slide 34

Slide 34 text

(번외) 안드로이드에서의 PIP ❏ PIP 원리 → 플로팅 뷰로 전환될 때, 액티비티가 리사이즈 된다고 생각하면 편함 ❏ 액티비티가 리사이즈 된다 == PIP 플로팅 뷰에서 보여줘야 할 뷰만 냅두고 나머지는 숨기던 해야함 ❏ PIP가 될 때, 최상위 뷰에서 PlayerView만 보이게 하고, PIP가 트리거 될 때 Lifecycle에 맞춰서 재생하던 설정하면 됨

Slide 35

Slide 35 text

이거 뭔가 아까운데 라이브러리로 공개 해볼까?

Slide 36

Slide 36 text

compose-video 라이브러리 배포하기 3.

Slide 37

Slide 37 text

라이브러리를 배포하기 위한 절차 ❏ Step 1. JavaDoc 호환 가능 주석 달기 ❏ Step 2. 라이브러리 샘플 앱 만들기 ( + 번외로 Baseline Profile 적용하기) ❏ Step 3. README.md 작성하기 ❏ Step 4. Maven Central 에 배포하기 ❏ Step 5-1. Maven Central Repository 가입 및 Namespace 인증 ❏ Step 5-2. GPG(GNU Privacy Guard) 프로젝트 서명 ❏ Step 5-3. 프로젝트에 Maven Central 배포 스크립트 추가 + 배포하기 ❏ Step 5. 배포 확인하기

Slide 38

Slide 38 text

라이브러리를 호스팅 하는 저장소 ❏ 여기서 다루는 배포 종류는 Maven Central로, 기존에는 jCenter도 유명했지만 지금은 서비스 종료 ❏ Git 기반으로 배포하는 jitpack도 있지만, 여기서는 다루지 않음

Slide 39

Slide 39 text

Maven Central 배포 방식의 변화 ❏ Maven Central의 기존 배포 방식은 Sonatype에서 관리하는 JIRA (프로젝트 관리 도구) 에서 직접 티켓을 생성하고, 인증하는 방식임 ❏ 이에 많은 사람들이 Maven Central에 배포하는 것을 어려워 함

Slide 40

Slide 40 text

Maven Central 배포 방식의 변화 ❏ Maven Central의 기존 배포 방식은 Sonatype에서 관리하는 JIRA (프로젝트 관리 도구) 에서 직접 티켓을 생성하고, 인증하는 방식임 ❏ 이에 많은 사람들이 Maven Central에 배포하는 것을 어려워 함 ❏ 그래서 이제 자동화 방식으로 변경되었으며, OSSRH → 중앙 포털로 배포하는 방식으로 변경되었음 ❏ https://central.sonatype.com/

Slide 41

Slide 41 text

Maven Central 배포 방식의 변화 ❏ Maven Central의 기존 배포 방식은 Sonatype에서 관리하는 JIRA (프로젝트 관리 도구) 에서 직접 티켓을 생성하고, 인증하는 방식임 ❏ 이에 많은 사람들이 Maven Central에 배포하는 것을 어려워 함 ❏ 그래서 이제 자동화 방식으로 변경되었으며, OSSRH → 중앙 포털로 배포하는 방식으로 변경되었음 ❏ https://central.sonatype.com/ ❏ 기존 유저만 OSSRH 접속 가능 + 사용 가능, 신규 유저 사용 불가

Slide 42

Slide 42 text

Maven Central Portal ❏ https://central.sonatype.com/ ❏ 문서 : https://central.sonatype.org/pages/support/ 메인 문서

Slide 43

Slide 43 text

배포 #1. Maven Central Portal 가입하기

Slide 44

Slide 44 text

배포 #2. Maven Central Portal Namespace 등록하기

Slide 45

Slide 45 text

배포 #2. Maven Central Portal Namespace 등록하기

Slide 46

Slide 46 text

배포 #2. Maven Central Portal Namespace 등록하기 도메인의 역순으로 입력하기 만약 Github 이라면 → io.github. 개인 도메인이 sanghun.io 이라면 → io.sanghun ex) kakao.com → com.kakao 등등

Slide 47

Slide 47 text

배포 #2. Maven Central Portal Namespace 등록하기

Slide 48

Slide 48 text

배포 #2. Maven Central Portal Namespace 등록하기 ❏ 만약 Github 이라면 → Verification Key와 동일한 이름의 Repository 만들기 ❏ io.sanghun 같은 도메인이라면 → 도메인 DNS TXT 레코드에 Verification Key 설정하기

Slide 49

Slide 49 text

배포 #2. Maven Central Portal Namespace 등록하기 ✅

Slide 50

Slide 50 text

배포 #3. JavaDoc 작성하기 ❏ Java / Kotlin 소스코드에서 /** summary */ 으로 시작하는 주석들 ❏ Java / Kotlin 소스코드에서 HTML 형태로 API 문서를 작성하는 것임 ❏ 컴파일 시점에서는 당연히 지워지는 영역

Slide 51

Slide 51 text

배포 #3. JavaDoc 작성하기

Slide 52

Slide 52 text

배포 #3. JavaDoc 작성하기 @SuppressLint("UnsafeOptInUsageError") @Composable internal fun VideoPlayerFullScreenDialog( player: ExoPlayer, currentPlayerView: PlayerView, fullScreenPlayerView: PlayerView.() -> Unit, controllerConfig: VideoPlayerControllerConfig, repeatMode: RepeatMode, resizeMode: ResizeMode, enablePip: Boolean, onDismissRequest: () -> Unit, securePolicy: SecureFlagPolicy, ) { ... }

Slide 53

Slide 53 text

/** * ExoPlayer does not support full screen views by default. * So create a full screen modal that wraps the Compose Dialog. * * Delegate all functions of the video controller that were used just before * the full screen to the video controller managed by that component. * Conversely, if the full screen dismissed, it will restore all the functions it delegated * for synchronization with the video controller on the full screen and the video controller on the previous screen. * * @param player Exoplayer instance. * @param currentPlayerView [androidx.media3.ui.PlayerView] instance currently in use for playback. * @param fullScreenPlayerView Callback to return all features to existing video player controller. * @param controllerConfig Player controller config. You can customize the Video Player Controller UI. * @param repeatMode Sets the content repeat mode. * @param enablePip Enable PIP. * @param onDismissRequest Callback that occurs when modals are closed. * @param securePolicy Policy on setting [android.view.WindowManager.LayoutParams.FLAG_SECURE] on a full screen dialog window. */

Slide 54

Slide 54 text

배포 #3. JavaDoc 작성하기 ❏ Java / Kotlin 소스코드에서 /** summary */ 으로 시작하는 주석들 ❏ Java / Kotlin 소스코드에서 HTML 형태로 API 문서를 작성하는 것임 ❏ 컴파일 시점에서는 당연히 지워지는 영역 ❏ 라이브러리에서 사용 가능한 공개 API에 JavaDoc 포맷에 맞게 작성하기 ❏ 라이브러리 내부에서 사용하는 Private API 에도 달아도 상관은 없음 ❏ JavaDoc을 작성하면, dokka 같은 API 문서 생성기가 생성도 해줌

Slide 55

Slide 55 text

(번외) dokka 예시 ❏ https://dsa28s.github.io/compose-video

Slide 56

Slide 56 text

(번외) dokka 예시

Slide 57

Slide 57 text

(번외) dokka 예시

Slide 58

Slide 58 text

배포 #4. PGP 서명 ❏ Maven Central Portal에 라이브러리를 배포하기 위해서는 라이브러리를 PGP 서명해야 함 ❏ GPG (GNU Privacy Guard)를 통해 서명할 수 있으며, 전자 서명에 사용되는 공개 키 암호화 도구임 ❏ GPG로 라이브러리를 서명 함으로써, 해당 라이브러리가 신뢰할 수 있는 것임을 증명함

Slide 59

Slide 59 text

배포 #4. PGP 서명 - 설치 ❏ macOS ❏ Homebrew 기준 brew install gnupg 로 설치함 ❏ Linux ❏ 이미 시스템에 설치되어 있을 수도 있음 ❏ 안되어 있다면 sudo apt install gnupg 로 설치 ❏ Windows ❏ https://gnupg.org/download/index.html#sec-1-2 에서 설치

Slide 60

Slide 60 text

배포 #4. PGP 서명 - 설치 확인 $ gpg --version gpg: WARNING: unsafe permissions on homedir '/Users//.gnupg' gpg (GnuPG) 2.4.5 libgcrypt 1.10.3 Copyright (C) 2024 g10 Code GmbH License GNU GPL-3.0-or-later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Home: /Users/dsa28s/.gnupg Supported algorithms: Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256 Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224 Compression: Uncompressed, ZIP, ZLIB, BZIP2

Slide 61

Slide 61 text

배포 #4. PGP 서명 - 키 만들기 $ gpg gen-key GnuPG needs to construct a user ID to identify your key. Real name: DoraDora Email address: [email protected] You selected this USER-ID: "DoraDora "

Slide 62

Slide 62 text

배포 #4. PGP 서명 - 만들어진 키 확인 $ gpg --list-keys pub ed25519 2024-06-07 [SC] [expires: 2027-06-07] 1CDC3050BAC59E93546A4C82F5DF465CF024EF5C uid [ultimate] DoraDora sub cv25519 2024-06-07 [E] [expires: 2027-06-07]

Slide 63

Slide 63 text

배포 #4. PGP 서명 - 만들어진 키 확인 $ gpg --list-keys pub ed25519 2024-06-07 [SC] [expires: 2027-06-07] 1CDC3050BAC59E93546A4C82F5DF465CF024EF5C uid [ultimate] DoraDora sub cv25519 2024-06-07 [E] [expires: 2027-06-07] 뒤 8자리 = Key ID 배포할 때 필요!!

Slide 64

Slide 64 text

배포 #4. PGP 서명 - 공개 키 배포 $ gpg --keyserver keyserver.ubuntu.com --send-keys KEY_ID pub ed25519 2024-06-07 [SC] [expires: 2027-06-07] 1CDC3050BAC59E93546A4C82F5DF465CF024EF5C uid [ultimate] DoraDora sub cv25519 2024-06-07 [E] [expires: 2027-06-07]

Slide 65

Slide 65 text

배포 #5. 플러그인 스크립트 추가 plugins { id "com.vanniktech.maven.publish" version "0.28.0" id 'signing' } build.gradle

Slide 66

Slide 66 text

배포 #5. 플러그인 스크립트 추가 group = 'io.sanghun' version = '1.3.0' build.gradle

Slide 67

Slide 67 text

배포 #5. 플러그인 스크립트 추가 mavenPublishing { // ... coordinates("io.sanghun", "compose-video", "1.3.0") // ... } build.gradle

Slide 68

Slide 68 text

pom { name = "compose-video" description = "Video UI Component for Jetpack Compose" inceptionYear = "2023" url = "https://github.com/dsa28s/compose-video" licenses { license { name = "The Apache License, Version 2.0" url = "https://www.apache.org/licenses/LICENSE-2.0.txt" distribution = "https://www.apache.org/licenses/LICENSE-2.0.txt" } } developers { developer { id = "dsa28s" name = "Dora Lee" url = "https://github.com/dsa28s" } } scm { url = "https://github.com/dsa28s/compose-video" connection = "scm:git:git://github.com/dsa28s" developerConnection = "scm:git:ssh://[email protected]/dsa28s" } }

Slide 69

Slide 69 text

배포 #6. 환경변수 설정 mavenCentralUsername=dsa28s mavenCentralPassword= signing.keyId= signing.password= signing.secretKeyRingFile=/Users//.gnupg/secring.gpg /.gradle/gradle.properties or local.properties

Slide 70

Slide 70 text

배포 #6. 찐 배포 $ ./gradlew publishAllPublicationsToMavenCentralRepository ❏ 위 명령줄이 완료된 후, Maven Central Portal에 들어가서 Deployments 확인하기 ❏ 확인 이후에는 Portal 에서 Publish 버튼을 누르면 최종 배포 완료!

Slide 71

Slide 71 text

배포 확인하기 implementation("io.sanghun:compose-video:1.3.0")

Slide 72

Slide 72 text

(번외) README.md도 쌈@뽕하게 작성하기 ❏ 라이브러리가 하는 핵심적인 역할 포함하기 ❏ 라이브러리 설치 방법 포함하기 ❏ (있다면) 라이브러리 스크린샷 / 샘플 앱 스크린샷 써서 라이브러리가 하는 역할 명확히 하기 ❏ 라이브러리에서 제공하는 API 설명서 포함하기 ❏ 라이브러리에서 제공하는 기능 단위로 열거하기 ❏ 라이선스 명시하기

Slide 73

Slide 73 text

(예시) README.md도 쌈@뽕하게 작성하기

Slide 74

Slide 74 text

(예시) README.md도 쌈@뽕하게 작성하기

Slide 75

Slide 75 text

compose-video 라이브러리의 미래 ❏ Kotlin Multiplatform 지원 하기 ❏ 하지만... 완전 시스템 종속 기능 + 비디오 플레이어 뷰를 어떻게 호스팅 해야할지 고민 필요 ❏ 커스텀 플레이어 뷰 가능토록 하게 하기 ❏ 지금은 ExoPlayer에서 제공중인 SimplePlayerView 기반으로 만들어져 있음 ❏ 나중에는 Compose 기반으로 플레이어 뷰를 만들 수 있도록 제공 예정 ❏ Android TV 지원 해보기

Slide 76

Slide 76 text

추가적으로 질문이 있으시다면? https://app.sli.do/event/8DRaXyE yfAvhsYqsL2XLE8 Slido (이번주 금요일까지 유효!)

Slide 77

Slide 77 text

compose-video 오픈소스 라이브러리 개발기 (배포까지) 이상훈 / at Studio