What’s new ExoPlayer in Google I/O 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 กันคร่าวๆไปแล้วเนอะ ตามนี้เลย
แปะรูปเทียบกันเลยดีกว่า
ส่วนบรรดา 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)
ถ้าไม่มีอะไรผิดพลาดจะได้แบบนี้มา
จบองก์วิดีโอหล่ะ ของเราโค้ดอยู่ที่ 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 เลี้ยวไปทางนั้นโล้ดเลย เราแบบฟังไป เขียนไป อาจจะตรงเขาบ้าง ไม่ตรงบ้าง
สุดท้ายฝากร้านกันสักนิด ฝากเพจด้วยนะจ๊ะ