มาเล่นวิดีโอด้วย ExoPlayer กันเถอะ
เมื่อมีท่านนึงบอกว่าอยากอ่านบล็อก ExoPlayer ที่เป็นภาษาไทย และน้องบอกว่า แฟนอยากอ่านมาก แต่เป็นภาษาอังกฤษเลยกดปิดไป เอออออ แปลไทยให้ก็ได้ค่ะ
เราร่างบล็อกนี้ระหว่างทำสไลด์เพื่อนำไปพูดในงาน Android Bangkok 2018 หรืองาน Droidcon ในวันที่ 31 มีนาคมที่ผ่านมา ในหัวข้อ ExoPlayer ซึ่งได้นำมาใช้ในฟังใจแทนเจ้า MediaPlayer และนำไปใช้กับ product ใหม่ด้วยหล่ะ เรานำเรื่องนี้มาพูด เนื่องจากเป็นเรื่องเดียวที่เตรียมทันนั่นเอง เย้
รู้จักเพลงวันแรกเนอะ ชอบไหม เอ้ยยยยย รู้ไหมว่าใน YouTube บน Android ใช้ playback library ของอะไร
เราเลยขอแบ่ง topic ย่อยๆออกเป็น 3 ส่วน คือ แนะนำ, ใช้ยังไง และ เพิ่มเติม แปลเป็นภาษาไทยดูแปลกๆดีเนอะ 555
ถ้าอยากอ่านภาษาอังกฤษ ที่นี่เลยจ้า
พร้อมหรือยังค่ะ มาเริ่มกันเลยดีกว่า
แนะนำ
ก่อนที่จะทำแอปที่เล่น media ทั้งหลาย เราต้องรู้ก่อนว่าเจ้า ExoPlayer มันคืออะไร
อะไรคือ ExoPlayer?
ExoPlayer คือ open source media playback library สำหรับ Android
พัฒนาโดย Google ซึ่งถูกเขียนด้วยภาษา JAVA และข้อดีก็มีมากกว่าเจ้า MediaPlayer เช่น เงียบง่าย, มีความยืนหยุ่น, และ เสถียร
Exoplayer มี features มากมาย เช่น เล่นวีดิโอหรือเสียงได้, กด shuffle, เล่นเพลงวนซํ้า, subtitle, playlist, caching/downloading, เล่น ads ต่างๆก็ได้ด้วย, ทำ streaming, album art, offline, cast extension และอื่นๆ
มา Recap จาก Google I/O 2017
เราได้ทำการ recapped ข้อมูลสำคัญจากงาน Google I/O 17 ในวันที่ 17 พฤษภาคม 2017 ซึ่งมี speaker สองท่านคือ Oliver Woodman และ Andrew Lewis เป็น developer ที่ทำเจ้า ExoPlayer นั่นเอง
Release Timeline
เรามาดูแต่ละ version ของ ExoPlayer กันดีกว่า ซึ่งตอน Google I/O ปีที่แล้วอยู่ในช่วง ExoPlayer 2.x พอดี และตอนนี้เป็น version 2.7.2 แล้ว (อัพก่อนวันงานวันนึง บ้าจริง)
- r1.2.3 (25 มีนาคม 15) : เป็น version แรกที่ปล่อยให้ developer ใช้
- r2.0.0 (14 กันยายน 16) : เป็น version แรกของ ExoPlayer 2.x ซึ่งเขาบอกว่าเลิกใช้ ExoPlayer 1.x ซะ
- 2.6.0 (23 พฤศจิกายน 17) : ตอนนี้เลิกมี r อยู่ข้างหน้าเลข version หล่ะ
- 2.7.0 (22 กุมภาพันธ์ 18) : มี feature ใหม่ๆเพียบ และ แก้บัคไปบางส่วน เช่น Player Interface, UI Component, Buffering, Cast Extension, Caching, และอื่นๆ ไปอ่านที่ release note เอานะ
- 2.7.2 (29 มีนาคม 18) : เวอร์ชั่นล่าสุดจ้าา มีแก้บัคเล็กน้อย
สำหรับ release date ของแต่ละ version เราดูที่นี่
และ release note
อันนี้จะบอกโดยรวมทั้งในส่วน feature และไฟล์ต่างๆที่ support เช่น streaming format, audio & video, subtitle, metadata, extension.
เปรียบเทียบ MediaPlayer vs ExoPlayer
- MediaPlayer รองรับทุก Android version เลย แต่ ExoPlayer รองรับ API level 16 ขึ้นไป
แต่ไม่ต้องกังวลไปเนอะ minimum android version ที่ใช้กันก็คือ API level 16 ซึ่งมีคนใช้ทั้งหมด 99.2% เรียกได้ว่าครอบคลุมยันจักรวาล
- MediaPlayer ใช้แบบท่ายากไม่ได้เลย เช่น adaptive playback (สำหรับ streaming formation เช่น smooth streaming, DASH, และ HLS), media composition, caching, และอื่นๆอีกมากมาย
- MediaPlayer เป็น black box ดังนั้นเราจะแก้อะไรไม่ได้เลย debug ก็ไม่ได้อีก แต่ ExoPlayer ตัวสไตล์สามารถปรับแต่งและขยายอะไรบางอย่างได้ด้วย
รูปนี้เปรียบเทียบให้เห็นเลยว่าระหว่าง MediaPlayer และ ExoPlayer อยู่ตรงไหนบ้าง สำหรับ MediaPlayer อยู่ใน Android OS และเหนือ Application เท่ากับว่า เราเรียกใช้ของสำเร็จรูป ส่วน ExoPlayer เราจะเรียกในแอปเรา และสามารถปรับอะไรได้ตามใจชอบเลย
แล้วแอปอะไรใช้ ExoPlayer บ้างอ่ะ?
ตอนแรก Google เริ่มพัฒนา ExoPlayer และนำไปใช้ใน YouTube, Google Play Movie, Google Photos, Youtube gaming, Google Play Newsstand ก่อนที่จะปล่อยให้ developer ทั้งหลายได้ใช้กัน
ใน Google Play Store นั้น มีมากกว่า 140,000 ที่ใช้ ExoPlayer ในแอปตัวเอง เพื่อเล่นไฟล์มีเดียต่างๆ เช่น Vevo, Twitter, BBC iPlayer, Netflix, Spotify, Facebook, Whatsapp, Twitch และรวมไปถึง Fungjai ด้วย
แล้วมันใช้ยังไงอ่ะ?
ExoPlayer รองรับ Android 4.1 (API level 16) ขึ้นไป แต่บางอันอาจจะรองรับ version ที่สูงกว่านี้ ตามรูปเลยจ้า
Configuration
เราเพิ่ม dependency ของ ExoPlayer ในไฟล์ build.gradle
ของ module ซึ่งเวอร์ชั่นล่าสุด คือ 2.7.2implementation 'com.google.android.exoplayer:exoplayer:2.7.2'
จากนั้นมาเพิ่ม internet permission ใน manifest file เพื่อรับไฟล์มีเดียต่างๆจาก url ถ้าจะทำการ caching หรือเล่นไฟล์มีเดียบนเครื่องของ user ก็เพิ่ม read/write storage เอานะ
<uses-permission android:name=”android.permission.INTERNET”/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Sample use
ใส่ลงไปใน layout file ได้เลยแบบนี้ ง่ายมากๆเลยใช่ไหมหล่ะตะเอง
<!-- activity_player.xml-->
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
เรามาเพิ่ม function สำหรับการอัญเชิญ player และสละมันทิ้งไปซะ
- เพิ่ม
initializePlayer()
สำหรับการสร้าง Exoplayer ใหม่ใน player view เรา set ให้มันเล่นทันทีเมื่อ player พร้อมทำงาน และ seek ไปยัง window ล่าสุด ถ้า player ของเราถูกสร้างแล้ว ก็เตรียม media source จาก url. - เพิ่ม
releasePlayer()
เพื่อรับ playback position, current window value, play หรือ pause state, release player และให้เจ้า player เป็น null ซะ
แล้วเราจะใช้เจ้า ExoPlayer ใน Activity/Fragment ได้อย่างไร?
- ใช้
initializePlayer()
ที่onStart()
และonResume()
สำหรับ init player - ใช้
releasePlayer()
ที่onPause()
และonStop()
สำหรับ release player ก่อนที่หน้า Activity/Fragment จะถูกทำลายไป
Playback States
สำหรับ playback states จะมี 4 states ใน player
- Idle (
Player.STATE_IDLE
) : ไม่ได้เล่น media ใดๆ - Buffering (
Player.STATE.BUFFERING
) : ขอโหลดข้อมูลเพิ่มหน่อยนะ - Ready (
Player.STATE_READY
) : เล่น media บน playback ได้หล่ะ - Ended (
Player.STATE_END
) : หยุดการเล่นที่ playback
ถ้าอยากจะจัดการอะไรพวกนี้ สามารถเข้าไปจัดการได้ที่ onPlayerStateChanged()
ซึ่งเราจะต้อง implementation เจ้ Player.EventListener
ก่อนนะ จึงจะใช้ได้
private String status;
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
switch (playbackState) {
case Player.STATE_BUFFERING:
status = PlaybackStatus.LOADING;
break;
case Player.STATE_ENDED:
status = PlaybackStatus.STOPPED;
break;
case Player.STATE_IDLE:
status = PlaybackStatus.IDLE;
break;
case Player.STATE_READY:
status = playWhenReady ? PlaybackStatus.PLAYING : PlaybackStatus.PAUSED;
break;
default:
status = PlaybackStatus.IDLE;
break;
}
}
จากด้านบน ผลสุดท้ายเราจะได้แอปที่สามารถ streaming audio หรือ video ได้ และมี media playback ด้วย เห็นม่ะๆ
แต่ๆๆๆๆๆๆๆ แต่ลืมอะไรไปหรือเปล่า ในตอนทำ initialize และ release player นั้น เราลืมอะไรไปเอ่ยยย คิดสิคิด!
Supported formats
ในฟังก์ชั่น initializePlayer()
เจ้า player ต้องมี MediaSource เพื่อเตรียม media จาก url และก็ supported formats ต่างๆกัน และใช้เจ้า MediaSource.Factory
ต่างกันด้วย
- แบบเบสิคๆก็ไฟล์ mp3 และ mp4 ไง :
return ExtractorMediaSource.Factory(new DefaultHttpDataSourceFactory(userAgent)).createMediaSource(uri)
- ถ้าเป็น playlist สำหรับ internet radio หรือ music streaming ก็ใช้เจ้า m3u8 นะ
return HlsMediaSource.Factory(DefaultHttpDataSourceFactory(userAgent)).createMediaSource(uri)
- พวก streaming technologies ยากๆทั้งหลาย เช่น DASH, SmoothStreaming และ HLS
val dashChunkSourceFactory = DefaultDashChunkSource.Factory(DefaultHttpDataSourceFactory("ua", BANDWIDTH_METER))
val manifestDataSourceFactory = DefaultHttpDataSourceFactory(userAgent)
return DashMediaSource.Factory(
dashChunkSourceFactory, manifestDataSourceFactory).createMediaSource(uri)
Bonus : เราสามารถเล่นไฟล์ audio/video บน Exoplayer จากไฟล์ที่อยู่ในเครื่อง user เองได้ด้วยนะ จากการใช้ Uri.fromFile
val bandwidthMeter = DefaultBandwidthMeter()
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory(bandwidthMeter)
val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
val defaultBandwidthMeter = DefaultBandwidthMeter()
val dataSourceFactory = DefaultDataSourceFactory(
context, Util.getUserAgent(context, "SongShakes"), defaultBandwidthMeter)
val videoSource = ExtractorMediaSource.Factory(dataSourceFactory).
createMediaSource(Uri.fromFile(File(filename)))
มาจัดการการหมุนจอ และไม่ให้เครื่องเรา lock screen ระหว่างที่เล่นวิดีโอ
เราดูวิดีโอไปเรื่อยๆ พอเราหมุนจอปุ๊ป อ้าว กลับไปเริ่มเล่นใหม่แหะ
แล้วเราจะแก้ปัญหานี้อย่างไรดีหล่ะ?
ตอนแรกคิดว่าจะใช้ Android Architecture Component แต่มันดูจัดการยุ่งยากซับซ้อน กับแค่อิหมุนจอเนี่ย เราก็ไม่ได้เก็บอะไรขนาดนั้นไง เอาจริงๆคือขี้เกียจ 555 (ถ้าว่างๆจะลองดูสักหน)
แต่เราขอใช้วิธีการแก้ปัญหาง่ายๆก่อนแล้วกัน ซึ่งมัน basic ที่สุดแล้ว และก็ไม่รู้ว่าคนอ่านจะ happy กับวิธีการแก้ปัญหาแบบนี้ไหมนะ การแก้ปัญหาในเบื้องต้น คือ การเพิ่ม config change สำหรับ player activity ที่ manifest file
<activity android:name=".PlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode">
</activity>
BONUS : ถ้าเราอยากเล่น video เต็มจอเฉพาะแนวนอนเท่านั้น และแนวตั้งยังเห็นเจ้า notification title ด้านบนอยู่ เราสามารถจัดการด้วย onConfigurationChanged
ว่าจอนั้นหมุนไปแนวไหน แล้วก็ handle เองตามนี้
ผลที่ได้ คือ เล่นวิดีโอต่อเนื่องระหว่างหมุนจอ แล้วก็เต็มจอเฉพาะแนวนอนอย่างเดียวด้วย
และเราใส่ android:keepScreenOn=“true”
ใน layout ที่เราวาง ExoPlayer activity_player.xml
เพื่อไม่ให้จอดับระหว่างเล่นวิดีโอ
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res
auto"android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true">
...
</LinearLayout>
มาแก้ปุ่มต่างๆของ playback บน player กันเถอะ
เราสามารถ custom user interface ของเจ้า player controller และ function ต่างๆ บน player ในแอปเรา
สำหรับการแก้ไขการทำงานต่างๆบน player : เราสามารถเพิ่ม behaviour attribute ของ playback ได้ ซึ่งทั้งหมดจะมีดังรูปนี้ และใครมันจะใช้มันทั้งหมดว่ะ จริงไหม
งั้นขอแสดงตัวอย่างหลังจากที่ใส่ฟังก์ชั่นบางอย่างเพิ่มไปด้วย
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="@+id/video_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:use_controller="false"
app:show_timeout="10000"
app:repeat_toggle_modes="one"
app:fastforward_increment="30000"
app:rewind_increment="30000"/>
app:use_controller
: default มันจะโชว์ playback controller ตลอดอยู่แล้ว ค่าเป็น “true” ถ้าจะซ่อนก็ใส่ “false” ซะapp:show_timeout
: เราจะให้ playback control ซ่อนหลังจากโชว์ให้ user ดูเป๋นเวลาเท่าไหร่ ในที่นี้ คือ 10 วินาทีapp:repeat_toggle_modes
: default มันจะไม่ repeat ค่าเป็น“none” ถ้าอยากให้เล่นซํ้าเพลงเดียว หรือวิดีโอเดียว เปลี่ยนค่าเป็น “one” และถ้าอยากให้วนซ้ทุกเพลงใน playlist ก็เปลี่ยนค่าเป็น “all”app:fastforward_incerment
: เวลาเรากดปุ่มนี้ มันจะเล่นวิดีโอข้างหน้า ในที่นี้ใช้ 10 วินาที คือกดแล้วมันจะไป 10 วินาทีให้หลังapp:rewind_increment
: เวลาเรากดปุ่มนี้ มันจะเล่นวิดีโอถอยหลัง ในที่นี้ใช้ 10 วินาที คือกดแล้วมันจะไป 10 วินาทีก่อนหน้าapp:resize_modes
: เป็นโหมดปรับขนาดของ player โดยปกติค่าจะเป็น “fit” ตาม ratio ของ video หรืออยากให้มัน fit ไปเท่ากับ layout ก็ทำได้ โดยเปลี่ยนค่าเป็น “fill”
สำหรับการเปลี่ยนปุ่ม player controller :
ก่อนอื่นสร้างไฟล์นี้ก่อนเลยจ้า exo_playback_control_view.xml
โดยเราจะทำการcustom playback style ด้วยวิธีการ override layout นั่นเองงง
เรามีสองตัวอย่างมาให้ดูกัน
ตัวอย่าง 1 :
- playback ของเรามีปุ่ม play/pause, forward, และ rewind
- เพิ่ม repeat button เข้าไปทีหลังจ้า
มาดูโดยรวมกันว่า ส่วนประกอบหลักๆของเจ้า exo_playback_control_view.xml
แบ่งเป็นสองส่วน คือ playback button และ timebar
เราสามารถแก้ปุ่มได้ทั้งหมด 7+1 ใน playback
- 7 ปุ่มหลัก : previous, rewind, shuffle, play, pause, forward, next
- 1 ปุ่มพิเศษ : repeat
สำหรับปุ่มหลัก ก็เพิ่มแสนจะง่ายเลย เพิ่มโดยการใส่เจ้า ImageButton แบบปกติเลย และสามารถแก้สี โดยใส่สีที่ต้องการที่ tint และรูปร่างปุ่มต่างๆเราใช้ style จาก ExoPlayer มาเลยจ้า
สำหรับปุ่ม repeat หา id มันเจอ แต่ไม่เจอสไตล์ของมันเลยแหะ งั้นใส่ style ของมันเป็น@Style/ExoMediaButton
แบบนี้ไปก่อนจ้า
แล้วไปเพิ่ม app:repeat_toggle_modes=”one”
ใน ExoPlayer บนไฟล์ player_activity.xml
เพื่อให้มันสามารถกดปุ่ม repeat ได้
สรุปไฟล์ layout ของ exo_playback_control_view.xml
สำหรับตัวอย่าง 1
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:background="#11000000"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal">
<ImageButton android:id="@id/exo_rew"
android:tint="#FF5D29C1"
style="@style/ExoMediaButton.Rewind"/>
<ImageButton android:id="@id/exo_repeat_toggle"
android:tint="#FF5D29C1"
style="@style/ExoMediaButton"/>
<ImageButton android:id="@id/exo_play"
android:tint="#FF5D29C1"
style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@id/exo_pause"
android:tint="#FF5D29C1"
style="@style/ExoMediaButton.Pause"/>
<ImageButton android:id="@id/exo_ffwd"
android:tint="#FF5D29C1"
style="@style/ExoMediaButton.FastForward"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFFFFFFF"/>
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="16dp"/>
<TextView android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFFFFFFF"/>
</LinearLayout>
</LinearLayout>
ตัวอย่าง 2 : แก้ปุ่ม play/pause และ timebar โดยได้รับคำบัญชาจาก UI designer
ขอแบ่งเป็น 2 ส่วนเพื่อความง่าย
(1) Playback : ก็แก้ ImageButton โดยการใส่ขนาด และ drawable ลงไป
<!-- Before -->
<ImageButton android:id="@id/exo_play"
android:tint="#FF5D29C1"
style="@style/ExoMediaButton.Play"/>
<!-- After -->
<ImageButton android:id="@id/exo_play"
android:layout_height="50dp"
android:layout_width="50dp"
android:background="@drawable/circle_purple_transparent"
android:src="@drawable/ic_play_player"/>
(2) TimeBar และ Duration:
สำหรับ duration นั้น แก้แบบ TextView ธรรมดาเลย
สำหรับ timebar สามารถเปลี่ยนขนาดและสี ที่ส่วนต่างๆใน timebar ได้เลย เช่น scrubber, played, unplay, และ buffer
Bonus : บางแอปเขาจะใช้ unplayed_color
และ buffered_color
สีเดียวกันนะ เราคิดว่าน่าจะขึ้นกับ UI/UX designer หรือ brand CI (ในที่นี้ คือ Color Index ไม่ใช่ Continuous Integration นะเออ)
สรุปไฟล์ layout ของ exo_playback_control_view.xml
สำหรับตัวอย่างที่ 2
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:background="#11000000"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal">
<ImageButton android:id="@id/exo_play"
android:layout_height="50dp"
android:layout_width="50dp"
android:background="@drawable/circle_purple_transparent"
android:src="@drawable/ic_play_player"/>
<ImageButton android:id="@id/exo_pause"
android:layout_height="50dp"
android:layout_width="50dp"
android:background="@drawable/circle_purple_transparent"
android:src="@drawable/ic_pause_player"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView android:id="@id/exo_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFFFFFFF"/>
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="16dp"
app:bar_height="1dp"
app:played_color="@color/colorAccent"
app:unplayed_color="#FFFFFFFF"
app:buffered_color="#FFFFFFFF"
app:scrubber_color="#FF5D29C1"/>
<TextView android:id="@id/exo_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFFFFFFF"/>
</LinearLayout>
</LinearLayout>
ปล. เราสามารถทำไฟล์ playback control viewใหม่ขึ้นมาได้เลยนะ และเพิ่มapp:controller_layout_id
บน ExoPlayer ของเราได้เลย แบบนี้
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
android:id="@+id/player_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:use_controller="true"
app:player_layout_id="@layout/exo_simple_player_view"
app:controller_layout_id="@layout/exo_playback_control_view"/>
Bonus : ถ้าเราอยากจะให้ play/pause โดยที่ไม่มี playback แบบนี้ เช่นมีแค่ปุ่มกดเล่น ระหว่างเล่นอยู่ปุ่มหายงี้ สามารถใส่โค้ดชุดนี้ลงไปได้เลย
การทำงาน player.playWhenReady = false
เมื่อหยุดชั่วคราวและ player.playWhenReady = true
เมื่อเล่นและเก็บ state ทั้งหมดไว้ใน playbackState
fun pausePlayer() {
player.playWhenReady = false
player.playbackState
}
fun startPlayer() {
player.playWhenReady = true
player.playbackState
}
ref:
Chunk List
ExoPlayer support chunk list สำหรับดาวน์โหลด stream media ในที่นี้เราพูดถึงการทำงานของ Chunk list นะ ไม่ใช่การใช้ Chunk ใน ExoPlayer นะ
ตัวอย่าง เราเล่นเพลงในเว็บ Fungjai (https://www.fungjai.com) สักเพลงนึง ระหว่างนั้นจะโหลด chunk list ขึ้นมา ดังด้านล่าง
มาอธิบายให้ชัดเจนกันดีกว่าหนาออเจ้า เช่น เราเล่น streaming music สักเพลงนึง เจ้า server จะแบ่งออกเป็นแต่ละส่วน เพื่อสามารถโหลด buffer มา ให้เล่นเพลงได้ smooth ซึ่งจะครอบคลุมเจ้า scrubber แต่ละก้อนจะเรียกว่า “Chunk”
อันนี้แอบกระซิบบอกนิดนึงตามประสาไม่เก่ง backend เหมือนจะใช้ wowza แบ่งโหลดนะ
สมมุติเราจิ้มไปตรงกลางๆเพลง server จะ check ดูว่า เห้ย มี buffer อยู่แถวนั้นไหม
อ้าว ไม่มี buffer ใช่ป่ะ โหลดมา
สำหรับขนาด นางจะแบ่งขนาดมาเท่าๆกัน เช่น ก้อนละ 10 วินาที ดังนั้นก้อนสุดท้ายมันไม่เท่ากับ 10 วินาทีเนอะ เพราะมันเป็นเศษเหลือนั่นเอง (ซึ่งเอาไปพูดเป็นอังกฤษยากมากเลยแหะ)
ใส่ Service ให้กับ Player กันดีกว่า
ฟังมาเยอะ อ่านมาเยอะ คงมีคำถามบางอย่างในใจกันใช่ม๊าา
- อยากให้เพลงเล่นตอนที่ lock screen ได้อ่ะ เล่นระหว่างอยู่แอปอื่นก็ได้ด้วยนะ
- พี่อยากได้ notification playback ด้วยนะ
- อะไรประมาณนี้อ่ะ
ถ้าอยากได้ตามข้างบน แอปเราก็ต้องมี service สิจ๊ะ
ประเภทของ Service มี 3 ประเภท คือ Background Service (ไม่ได้ทำงานโดยตรงกับ user), Foreground Service (โชว์ serviceให้ user ดู แล้วมีพวก status bar icon มาด้วย), และ Bound Service (มีการคุยกันระหว่าง client และ server)
ดังนั้นแอปนี้ใช้ bound service เพราะว่ามีการคุยกันระหว่าง component และ service ตลอดเวลา และทำงานเป็น queue
ตัวอย่างแอปที่ใช้ bound service ในการเล่นเพลงแบบ background
การทำงานของ Service บน Exoplayer
PlayerNotificationManager
เป็นคลาสที่จัดการเกี่ยวกับ notification playback ใน bound servicePlayerService
จัดการการทำงานของ service บน playerPlayerManager
จัดการเกี่ยวกับ service และถูกเรียกด้วยActivity/Fragment ที่ใช้ service
ตอนแรกสร้างไฟล์ PlayerService.java
เพื่อสร้าง service ในแอปของเรา
public class PlayerService extends Service implements AudioManager.OnAudioFocusChangeListener, Player.EventListener {
...
}
และเพิ่มใน manifest file แบบนี้
<service android:name=".player.PlayerService"/>
ดังนั้นเราจะสร้าง bound service ตาม lifecycle ด้านขวาของรูปเลยเจ้าาา
onCreate()
: setup สิ่งจำเป็นที่ใช้ใน service เช่น media notification manager, media session, และ Exoplayer
onBind()
: bind service โดย component จากการเรียกbindService()
และส่ง data ที่ใช้โดย Intent.
ในเคสนี้จะคืนเจ้า service ของ class นี้
protected class PlayerBinder extends Binder {
PlayerService getService() {
return PlayerService.this;
}
}
private final IBinder playerBind = new PlayerBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return playerBind;
}
onUnbind()
: unbind service โดย component จากการเรียกunbindService()
และส่ง boolean มาตัวนึงเพื่อ rebind service
ในเคสนี้ ถ้า playback status เราเป็น idle, player service จะหยุดจ้า
@Override
public boolean onUnbind(Intent intent) {
if (status.equals(PlaybackStatus.IDLE))
stopSelf();
return super.onUnbind(intent);
}
onDestroy()
: reset value และคืน resource ไปสู่ system
ในเคสนี้ จะ release และลบ listener ใน player, ยกเลิก notify notification playback, และ release media session
@Override
public void onDestroy() {
pause();
exoPlayer.release();
exoPlayer.removeListener(this);
notificationManager.cancelNotify();
mediaSession.release();
super.onDestroy();
}
วิธีการใช้ service?
- เราใช้
service.bind()
ในที่ที่เราต้องการใช้ service และservice.unbind()
สำหรับเลิกใช้ service
class PlayerFragment : Fragment() {
lateinit var playerManager: PlayerManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
playerManager = PlayerManager.with(context)
playerManager.bind()
...
}
...
override fun onStop() {
super.onStop()
playerManager.unbind()
}
}
เรื่องนี้รู้สึกอธิบายยากจังเลย โค้ดก็เยอะมาก อ่านแล้วคงจะงงกันใช่ไหมหล่ะ
เราจะนำคุณไปสู่โปรเจกของเรา เพื่อจะมาดูโค้ดกัน
และดูได้ที่นี้เลยจ้า โอ้ยยยย เหนื่อยจังเลย
เพิ่มเติม : สำหรับข้อมูลเพิ่มเติมในเรื่องนี้
เราเก็บรวบรวมข้อมูล ExoPlayer ที่เราใช้ เยอะแยะมากมาย ดังนี้
Official ExoPlayer Resource
- ExoPlayer github
และ guideline
- developer ของ ExoPlayer เขาเขียน blog บน medium ด้วยนะเออ ในนั้นบอกถึง feature ใหม่ของบาง version วิธีการใช้ feature ต่างๆ tutorial ต่างๆ
- อ่านแล้วอยากลองเขียนใช่ไหมหล่ะ เริ่มจากเจ้า codelab ก่อนเลย
libraries ที่น่าสนใจ
- อันนี้ custom ExoPlayer ซึ่งใช้ง่ายยังกะ VideoView
สำหรับ trim video library ใช้ตัวนี้
- อยากเพิ่ม filter video ใช้ตัวนี้เลยจ้าาา
และสามารถเซฟวิดีโอ with filter ได้ด้วย แต่ใช้ตัวนี้นะ
แต่ถ้าเราเซฟวิดีโอที่มี filter และลายนํ้า เราต้องเซฟสองรอบอ่ะ เพราะมันมองลายนํ้าเป็น filter ตัวนึง แถม process นานอีก ฮือออ
- สำหรับ Caching ใน ExoPlayer บรรดา developer มีปัญหากันหลายๆ issue เกิดจากความไม่เคลียร์ในการใช้งาน จนมีคนนึงแนะนำว่าใช้ library ตัวนี้สิ แต่ก็มีปัญหา คือ cahce video ทั้งไฟล์เลยจ้า กิน storage เราไปอีกกก
ดังนั้น developer ของ Exoplayer เขียนบล็อก tutorial สำหรับ caching บน medium ซะเลย บ่นกันเยอะใช่ไหม ห๊ะ
- สิ่งที่เราสนใจอีกเรื่อง คือ cast extension, สามารถ cast จากมือถือของเรา ไป smart device อื่นๆได้ เช่น ทีวี
สุดท้าย #พื้นที่โฆษณา
Fungjai เป็นแอปฟังเพลงแบบ streaming ทำโดยคนไทย 100% เลย ศิลปินที่มาลงเพลงนั้นนอกจากคนไทยยังมีชาติอื่นๆอีกด้วย เช่น ญี่ปุ่น ไต้หวัน อินโดนีเซีย และมีหลายๆ feature ที่น่าสนใจ ดังนี้
- Discover : Top 20 charts, Recommend, New Album, และ New Artist
- Browse : สามารถเลือกเพลงตามอารมณ์หรือแนวเพลงได้
- Playlist : จัดโดย Fungjai staff นั่นแหละ แบ่งเป็น 3 type ยังกะ photobook BNK48 เลย มี Mood & Genre Playlist, Theme Playlist (heartbroken), และ Feature Playlist (Recommend song, re-live, DJ).
- My Music : บรรดาเพลงที่ชอบ เพลย์ลิสต์ที่ชอบ อัมบั้มที่ชอบ และเพลงที่ฟังน่าสุด มีฟังออฟไลน์ด้วยนะ
แอปฟังใจใช้ ExoPlayer กับตัว player นั่นแหละ และเพิ่ม service ตามที่เรากล่าวไปเบื้องต้น
ปล. กด like หรือ follow เพจของเราได้น๊าาาา เราจะลงบทความเราเองด้วย และบทความที่น่าสนใจจากท่านอื่นๆที่เราอยากแบ่งปัน