What’s new ExoPlayer in Google I/O 2018

Minseo Chayabanjonglerd
Black Lens
Published in
6 min readDec 26, 2018

หลังจากที่พูดจากประสบการณ์ไปเมื่องาน Android Bangkok 2018 ไปแล้ว เราแอบเห็นว่าแอบมีอะไรเพิ่มมา เอ๊ะ มันจะง่ายกับนักพัฒนาแบบเราไหมนะ

มีคนบ่น ทำไมบล็อก ExoPlayer ที่เราเขียนเนื้อหาที่พูดในงาน Android Bangkok 2018 เป็นภาษาอังกฤษหล่ะ แงๆๆๆๆ

ภาษาไทยก็มีจ้าาา

อันนี้ของปีก่อน เป็นการเปิด grand opening เจ้า ExoPlayer เนื้อหาไปแนวปูพื้นฐานการทำงานอย่างแท้จริง

และนี่ของปีนี้ โชว์ live code ทำแอปให้เราดูเลยจ้า มี service มาให้ด้วย เอ๊ะทำไมคล้ายๆฟังใจจังเลย แต่ handle คนละอย่างแน่นอน

ดังนั้นเนื้อหาคิดว่าน่าจะมีแบบปูจาก basic เหมือนที่พูดใน session นั้น บวกกับการเปรียบเทียบระหว่างสองปีแบบคร่าวๆ และของที่เขาโชว์เทพในปีนี้ แถมมี source code มาให้ลองเล่นด้วย แต่ขัดใจนิดนึง ยังเป็น Java อยู่เบย ซึ่งตัว library เขาก็เขียนด้วย Java นี่แหละ

ไปๆมาๆแทบจะจัด workshop session ได้เลยนะนี่

และบล็อกนี้ใช้เวลาเขียนยาวนานพอสมควรเลยนะ กว่าจะเขียนจบได้นี่อย่างเหนื่อย

สถิติที่งดงาม

เราก็พอจะทราบเกี่ยวกับ ExoPlayer กันคร่าวๆไปแล้วเนอะ ตามนี้เลย

แปะรูปเทียบกันเลยดีกว่า

ปีก่อนแสนสี่ ก่อนนี้ทุ่งทะยานเกินสองแสนแอปแล้วจ้า ที่ใช้ ExoPlayer

ส่วนบรรดา feature ต่างๆนั้น ก็มีเพิ่มเติมมาบางส่วนจากหลัง IO 17 เช่น shuffle (เคยลองใช้แต่ยังไม่ชอบ ไม่รู้ Spotify ใช้ตัวนี้ไหม ของเขา smooth มากอ่ะ), repeat, offline, ที่เราแอบเห็นมาก่อนหน้านี้ที่เขาจะทำ แต่ยังไม่ release ออกมา ไม่ว่าจะเป็น cast หรือพวกที่จัดการ service ต่างๆ

ซึ่งสไลด์ของปีนี้เขียนชัดเจนดี ว่าหยิบตัวไหน ได้อะไรไปใช้บ้าง

มาเริ่มทดลองทำกันดีกว่าจ้า

หลังจากพูดรายละเอียดเสร็จสรรพ พี่แก live code โชว์เลยจ้า

ย้อนความนิดนึง ด้วยความขี้เกียจและไม่ค่อยเข้าใจในการเรียกแยก เลยเรียกรวมแบบนี้

implementation 'com.google.android.exoplayer:exoplayer:2.8.0

แต่จริงๆสามารถเรียก dependency ตามการใช้งานได้แล้วนะ เช่น ในที่นี้อยากใช้แบบพื้นฐานและมีหน้าตาของ player ด้วย ไปที่ build.gradle ของ module และใส่ ExoPlayer ลงไปใน dependencies ซะ

dependencies {
implementation 'com.google.android.exoplayer:exoplayer-core:2.8.0'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.8.0'
}

จากนั้นใส่ playerView ที่หน้า layout

<com.google.android.exoplayer2.ui.PlayerView
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

ด้วยความที่โค้ดมันเป็น Kotlin ดังนั้นบางอย่างก็อาจจะไม่เหมือนกันเนอะ โดยเรา focus แค่ 2 จุด คือ onStart() ตอนเริ่มสร้าง player และ onStop() ตอนที่เราออกจากแอป จะทำลาย player ทิ้งไป

ถ้าไม่มีอะไรผิดพลาด player เราจะเล่นมีเดียได้แบบนี้

ว่าง่ายๆเลยคือถ้าถูกต้องก็จะเล่นวิดีโอให้เราเลยงี้

การใส่ Video Ads

ก่อนอื่นเพิ่มสิ่งนี้ใน build.gradle ก่อนจ้า

dependencies {
implementation 'com.google.android.exoplayer:extension-ima:2.8.0'
}

เพิ่ม Video Ads โดยการสร้างเจ้า ImaAdsLoader ขึ้นมาตัวนึง และสร้างที่ onCreate() นะ ใส่ parameter 2 ตัวลงไป คือ context และ ที่อยู่ Video Ads ของเรา

adsLoader = ImaAdsLoader(this, Samples.AD_TAG_URI)

และตอนปิดแอปก็ให้มันจากไปอย่างถูกต้องที่ onDestroy()

adsLoader.release()

การเรียกใช้งานนั้น เราสร้าง adsMediaSource มา โดยใส่ mediaSource ที่เราสร้างไว้ ก็คือวิดีโอที่เราจะเล่น ตามด้วย dataSourceFactory พร้อมด้วยเจ้า adsLoader ที่เราเพิ่งสร้าง และใส่ที่ player view ของเรา และไปใส่ให้มันเตรียมเจ้า adsMediaSource เพื่อนำไปเล่นทั้งหมด เมื่อ player พร้อมใช้งาน

val adsMediaSource = AdsMediaSource(mediaSource, 
dataSourceFactory,
adsLoader,
playerView.overlayFrameLayout)

player?.prepare(adsMediaSource)

ถ้าไม่มีอะไรผิดพลาดจะได้แบบนี้มา

ในตอนแรกแอปจะเล่น video ads ซึ่งบางอันมีแบบต่อเนื่องด้วยแหะ เวลากดที่ player ตอนเล่น ads เราจะทำอะไรกับมันไม่ได้เลย พอกลับมาเล่นตัว video ปกติ ก็จะเห็นจุดเหลืองๆที่ Timebar นั่นคือตำแหน่งที่ ads ต่อๆไปจะเล่นนั้นเอง ซึ่งก็เหมือน Youtube แหละจ้า

จบองก์วิดีโอหล่ะ ของเราโค้ดอยู่ที่ branch video นะ ไม่ใช่ไรหรอก สร้างไปแล้ว คิดไม่ทัน อ่ะแตก branch เลยแล้วกัน ฮ่าๆ ขี้เกียจสร้าง

ปล. ยังมีความลองทำแล้วไม่เล่นวีดีโออ่ะในตอนแรก พอมาวันรุ่งขึ้นเปิดอีกทีได้เฉย ไม่รู้จะโทษอะไรดี ฮ่าๆ หัวเสียทำไมตั้ง 2 ชั่วโมง

มาสร้างแอปฟังเพลงกันเถอะ

คร่าวๆคือแอปฟังเพลงอะเนอะ ที่มี service และ notification แสดงด้วยหล่ะ ตัวโค้ดที่เห็นผ่านตาคร่าวๆจะสร้าง object class เพื่อ mock data ไว้ แล้วดันเขียนแบบไวๆโดยใช้ ListView อ่ะ ฮืออออออออ แต่มันไม่ใช่สาระสำคัญในตอนนี้อ่ะเนอะ

เจ้า Notification ที่ว่านี้ เป็น Foreground Service เนอะ
ดังนั้นจึงมีเจ้า AudioPlayerService โผล่มา เรามาเรียกใช้ player ใน Service และมีส่วนประกอบดังนี้

และอย่าลืมนำ class service ไปใส่ใน AndroidManifest.xml ด้วยนะ

<service android:name=".AudioPlayerService"/>

หน้า MainActivity นั้น เราสร้างเจ้า intent มาตัวนึง ที่บรรจุเจ้า service ที่เราเพิ่งสร้าง

val intent = Intent(this, AudioPlayerService::class.java)

จากนั้นเรียกใช้ Foreground service ของเจ้า ExoPlayer

Util.startForegroundService(this, intent)

แต่เดี๋ยวก่อนนนน มันจะใช้เล่นเลยไม่ได้นะ เนื่องจากตัวอย่างนี้มันเป็น playlist 3 เพลง ดังนั้นจึงสร้างเจ้า ConcatenatingMediaSource คือเจ้า playlist ของเรา แล้วเอาเจ้า mediaSource ของแต่ละตัวมาใส่ในเจ้านี่ เหมือนเราเพิ่มเพลงมาใส่ใน playlist โค้ดจะเป็นแบบนี้นะ

ขั้นตอนต่อมา เนื่องจากเราจะทำเจ้า Foreground Service ซึ่งมี Notification เนอะ โดยปกติจะสร้าง class Notification แยกมา แล้วเอาไปเรียกใช้ แต่ตอนนี้ ExoPlayer เขาทำมาให้แล้ว เย้ๆ การใช้งานก็แสนจะง่าย เพียงเพิ่มสิ่งนี้ไปใน service เท่านั้น

สร้างเจ้า PlayerNotificationManager ขึ้นมา

  • getCurrentContentTitle : เป็นตัว Title บอกว่าเพลงนี้ชื่อเพลงว่าอะไร
  • createCurrentContentIntent : คืนค่าเป็นเจ้า PendingIntent ว่าเริ่มขึ้น Notification ที่ class ไหน
  • getCurrentContextText : บอกคำบรรยาย อาจจะเป็นชื่อศิลปิน ชื่ออัมบั้มก็ได้นะ
  • getCurrentLargeIcon : พวก Artwork ต่างๆ ซึ่งเจ้า ExoPlayer มันจะรับเป็น Bitmap อยู่แล้วนะ (เดี๋ยวอธิบายการใช้งานเพิ่มทีหลังจ้า)

การใช้งาน set listener ของเจ้า Notification เสียก่อน ซึ่งจะมี 2 ส่วน ที่ตรงตัวมากๆ คือตอนเริ่มสร้าง notification ก็ให้ startForeground เลย และถ้าถูก cancel ก็ให้หยุดตัวเอง จากนั้นก็ set player

ตอนที่เจ้า Service ถูกทำลาย ก็ใส่ set player เป็น null ซะ

จากนั้นลองรันดูจ้า ถ้าทำถูกต้องนั้นจะสามารถฟังเพลงได้จ้า ตัว noti สามารถกดแค่ซ้ายสุดกับขวาสุด แล้วตัว cover, name, description เปลี่ยนไปตามเพลงเนอะ แล้วมันปิดและปัดทิ้งออกไม่ได้เมื่อออกแอปอ่า

ในส่วนนี้สามารถเอาโค้ดไปศึกษาได้ที่นี่นะ

เอาจริงๆนะ แค่นี้ก็น่าจะเพียงพอต่อการใช้งานเบื้องต้นสำหรับการทำแอปฟังเพลง แต่ๆๆๆๆๆๆ แต่ยังไม่จบจ้า

เจ้า Service ที่เราสร้างไปนั้น มีประโยชน์หลายอย่างเลยจ้า คือ สามารถใช้กับอะไรก็ได้ เช่น Google Assistant สั่งงานด้วยเสียง และสามารถสั่งให้เล่นที่ Android Auto, Wear ได้ และต้องคำนึงถึง 3 ข้อ คือ (1) รู้ state ของ playback (2) สามารถสั่งงานจากแอปภายนอกได้ (3) สามารถเรียกดู media catalog ได้ ซึ่งคนที่ทำเป็นคือ Media Session แต่เราจะไม่ใช้งานนางโดยตรงนะ ดังนั้นจะสร้างเจ้า Connector ขึ้นมาเพื่อจัดการสิ่งนี้

ว่าแล้วมาลุยโค้ดกันต่อจ้า

สร้างเจ้า MediaSessionCompat มาตัวนึง ให้มัน active เพื่อให้เจ้า player notification manager เกิดมา ตอน lock screen ก็ยังเห็นมันทำงานอยู่ ประมาณนี้

val mediaSession = MediaSessionCompat(context, MEDIA_SESSION_TAG)
mediaSession.isActive = true

และสร้างเจ้า MediaSessionConnector ขึ้นมาเพื่อเชื่อมต่อ playlist ที่เรากำลังจะเล่น

playerNotificationManager.setMediaSessionToken(mediaSession.sessionToken)

และสร้าง queue เพลง โดยมีเจ้า timeline เข้าช่วย สิ่งที่ได้กลับมาคือเพลงนี้อยู่ตำแหน่งที่เท่าไหร่ของ playlist และรายละเอียดของเพลงนั้นๆ

mediaSessionConnector = MediaSessionConnector(mediaSession)
mediaSessionConnector.setQueueNavigator(object: TimelineQueueNavigator(mediaSession) {
override fun getMediaDescription(player: Player?, windowIndex: Int): MediaDescriptionCompat {
return Samples.getMediaDescription(context, SAMPLES[windowIndex])
}

})

แน่นอนเราจะใส่มันเพิ่มไม่ได้ ถ้าไม่ได้เพิ่มสิ่งนี้

implementation 'com.google.android.exoplayer:extension-mediasession:2.8.1'

เรียกได้ว่าโดนวางยา นั่นเอง แถมเจ้า getMediaDescription ได้แอบสร้างเอาไว้แล้ว

จากนั้นทำการ sync player ด้วย MediaSession

mediaSessionConnector.setPlayer(player, null)

แน่นอนว่าทั้งหมดสร้างแล้วต้องลบทิ้งใน onDestroy นะ

override fun onDestroy() {
mediaSession.release()
mediaSessionConnector.setPlayer(null, null)
playerNotificationManager.setPlayer(null)
player.release()
super.onDestroy()
}

ลอง build ดูสิ ;) build เสร็จเล่นเพลง แถมขึ้น noti จริงจังไปอีกกก แน่นอนกดออกแอปก็ยังเล่นต่อเนอะ แต่ปัดออกไม่ได้จ้าาา แง ต้องกดไปเล่นเพลงสุดท้ายแล้ว stop อ่ะ

สุดท้าย offline music แอปเพลงหลายแอปชอบทำกัน บางคนไม่ได้มี 4G ตลอดเวลา หรือเน็ตตาย หรืออยากไปฟังในป่างี้อ่ะเนอะ ซึ่ง version 2.8.0 มัน support เรื่องของ caching ด้วยนะ

เขาโชว์ data การเล่นเพลง mp3 มาให้เราดู เพลงต่างๆก็จะอยู่บนท้องฟ้า….. บน cloud แล้วก็เข้ามาในส่วน MediaSource แล้วเอาไปเล่นที่ player

อันนี้เล่าคร่าวๆนะ เริ่มเหนื่อย 555 ใน ExoPlayer จะมีตัวช่วยตัวนึง ชื่อว่า CacheDataSource อยู่คั่นระหว่างเจ้า DataSource และ MediaSource นางจะหา cache และเก็บเอาไว้เองไว้ใน local storage เนอะ ถ้าข้อมูลที่ player เรียกมา มันไม่อยู่ใน cache มันก็จะดึงมาจาก MediaSource ปกติ

ส่วน download มันก็ผ่านเจ้า CacheDataSource เช่นกัน

มาเขียนโค้ดกันเถอะ

ให้เจ้า player มัน support caching การทำงานจะทำบน background

และเขาก็ลักไก่ไปสร้าง class แยกอีกเช่นเคย เพื่อรับ cache เข้ามา

และสร้างเจ้า CacheDataSourceFactory มาตัวนึง และเสียบเข้า MediaSource

val cacheDataSourceFactory = CacheDataSourceFactory(DownloadUtil().getCache(context),
dataSourceFactory,
CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)

val concatenatingMediaSource = ConcatenatingMediaSource()
for (sample in SAMPLES) {
val mediaSource = ExtractorMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(sample.uri)
concatenatingMediaSource.addMediaSource(mediaSource)
}

และสร้าง AudioDownloadService และเพิ่มลง AndroidManifest ด้วย

จากนั้นก็มา implement กันต่อ ในหน้า MainActivity ก็ให้แสดงเพลงในหน้าแรก จากนั้นให้กดแต่ละเพลงเพื่อเล่น แล้วก็ download

ผลออกมาเป็นดังนี้ เวลาเรากดไปที่ listview ตัวนึง แล้วจะมี Notification เด้งขึ้นมา 2 ตัว ตัวแรกก็เป็นตัว player notification ตัวที่สองคือ ตัว download cache นั่นเอง

และ code ส่วน audio ทั้งหมดอยู่ในนี้

สุดท้ายจริงๆ เขากล่างถึง Google Cast ซึ่งจะมี Cast Extension มาให้เราใช้ใน ExoPlayer ด้วยยย ซึ่งเขาก็เล่าเกี่ยวกับการทำงานคร่าวๆ ส่วน demo และ blog ก็ตามดูทีหลังได้นะ

และทั้งหมดทั้งมวลอยู่ในนี้จ้าาา

จบแล้วจ้าการสรุป ExoPlayer ใน IO 18 เหนื่อยมากกกกกกกกกกกกกกกกก

ใช้เวลาสรุปนี่หลายเดือนมากกว่าจะเสร็จอ่ะ

ปล. มีคนขัดใจก่อนเราที่โค้ดเป็น Java เลยส่ง issue พร้อม pull code อันที่เป็น Kotlin มาให้ ถ้าใครเขียน Kotlin เลี้ยวไปทางนั้นโล้ดเลย เราแบบฟังไป เขียนไป อาจจะตรงเขาบ้าง ไม่ตรงบ้าง

สุดท้ายฝากร้านกันสักนิด ฝากเพจด้วยนะจ๊ะ

--

--

Minseo Chayabanjonglerd
Black Lens

Android Developer | Content Creator AKA. MikkiPastel | Web2 & Web3 Contributor