การปรับแต่ง

หัวใจสำคัญของไลบรารี ExoPlayer คืออินเทอร์เฟซ Player Player จะแสดงฟังก์ชันการทำงานของมีเดียเพลเยอร์ระดับสูงแบบดั้งเดิม เช่น ความสามารถในการบัฟเฟอร์สื่อ เล่น หยุดชั่วคราว และกรอ การติดตั้งใช้งานเริ่มต้น ExoPlayer ได้รับการออกแบบมาให้มีสมมติฐานเพียงเล็กน้อยเกี่ยวกับ (และด้วยเหตุนี้จึงมีข้อจำกัดเพียงเล็กน้อยเกี่ยวกับ) ประเภทของสื่อที่เล่น วิธีการและตำแหน่งที่จัดเก็บ และวิธีการแสดงผล แทนที่จะใช้การโหลดและการแสดงผลสื่อโดยตรง การติดตั้งใช้งาน ExoPlayer จะมอบสิทธิ์งานนี้ให้กับคอมโพเนนต์ที่แทรกเมื่อสร้างเพลเยอร์หรือเมื่อส่งแหล่งที่มาของสื่อใหม่ไปยังเพลเยอร์ คอมโพเนนต์ที่ใช้ร่วมกันในการติดตั้งใช้งาน ExoPlayer ทั้งหมดมีดังนี้

  • อินสแตนซ์ MediaSource ที่กำหนดสื่อที่จะเล่น โหลดสื่อ และอ่านสื่อที่โหลดได้ อินสแตนซ์ MediaSource สร้างขึ้นจาก MediaItem โดย MediaSource.Factory ภายในเพลเยอร์ นอกจากนี้ยังส่งไปยังเพลเยอร์ได้โดยตรงโดยใช้ API เพลย์ลิสต์ตามแหล่งที่มาของสื่อ
  • อินสแตนซ์ MediaSource.Factory ที่แปลง MediaItem เป็น MediaSource ระบบจะแทรก MediaSource.Factory เมื่อสร้างเพลเยอร์
  • อินสแตนซ์ Renderer ที่แสดงผลคอมโพเนนต์แต่ละรายการของสื่อ ระบบจะแทรกข้อมูลเหล่านี้เมื่อสร้างเพลเยอร์
  • TrackSelector ที่เลือกแทร็กที่ MediaSource มีให้เพื่อใช้โดย Renderer ที่พร้อมใช้งานแต่ละรายการ ระบบจะแทรก TrackSelector เมื่อสร้างเพลเยอร์
  • LoadControl ที่ควบคุมเวลาที่ MediaSource บัฟเฟอร์สื่อเพิ่มเติม และปริมาณสื่อที่บัฟเฟอร์ ระบบจะแทรก LoadControl เมื่อสร้างเพลเยอร์
  • LivePlaybackSpeedControl ที่ควบคุมความเร็วในการเล่นระหว่างการเล่นแบบสดเพื่อให้เพลเยอร์อยู่ใกล้กับออฟเซ็ตแบบสดที่กำหนดค่าไว้ ระบบจะแทรก LivePlaybackSpeedControl เมื่อสร้างเพลเยอร์

แนวคิดของการแทรกคอมโพเนนต์ที่ใช้ฟังก์ชันการทำงานของเพลเยอร์มีอยู่ทั่วทั้งไลบรารี การติดตั้งใช้งานเริ่มต้นของคอมโพเนนต์บางรายการจะมอบสิทธิ์การทำงานให้กับคอมโพเนนต์ที่แทรกเพิ่มเติม ซึ่งช่วยให้สามารถแทนที่คอมโพเนนต์ย่อยจำนวนมากด้วยการติดตั้งใช้งานที่กำหนดค่าในลักษณะที่กำหนดเองได้ทีละรายการ

การปรับแต่งเพลเยอร์

ตัวอย่างที่พบบ่อยของการปรับแต่งเพลเยอร์โดยการแทรกคอมโพเนนต์มีอธิบายไว้ในส่วนต่อไปนี้

กำลังกำหนดค่าสแต็กเครือข่าย

เรามีหน้าเกี่ยวกับการปรับแต่งสแต็กเครือข่ายที่ ExoPlayer ใช้

กำลังแคชข้อมูลที่โหลดจากเครือข่าย

ดูคำแนะนำสำหรับการแคชแบบ On-the-fly ชั่วคราวและการดาวน์โหลดสื่อ

การปรับแต่งการโต้ตอบกับเซิร์ฟเวอร์

แอปบางแอปอาจต้องการสกัดกั้นคำขอและการตอบกลับ HTTP คุณอาจต้องการแทรกส่วนหัวของคำขอที่กำหนดเอง อ่านส่วนหัวการตอบกลับของเซิร์ฟเวอร์ แก้ไข URI ของคำขอ ฯลฯ ตัวอย่างเช่น แอปอาจตรวจสอบสิทธิ์ตัวเองโดยแทรกโทเค็นเป็นส่วนหัวเมื่อขอเซกเมนต์สื่อ

ตัวอย่างต่อไปนี้แสดงวิธีใช้ลักษณะการทำงานเหล่านี้โดยการแทรก DataSource.Factory ที่กำหนดเองลงใน DefaultMediaSourceFactory

Kotlin

val dataSourceFactory = DataSource.Factory {
  val dataSource = httpDataSourceFactory.createDataSource()
  // Set a custom authentication request header.
  dataSource.setRequestProperty("Header", "Value")
  dataSource
}
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
    )
    .build()

Java

DataSource.Factory dataSourceFactory =
    () -> {
      HttpDataSource dataSource = httpDataSourceFactory.createDataSource();
      // Set a custom authentication request header.
      dataSource.setRequestProperty("Header", "Value");
      return dataSource;
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory))
        .build();

ในข้อมูลโค้ดก่อนหน้า HttpDataSource ที่แทรกไว้จะรวมส่วนหัว "Header: Value" ไว้ในคำขอ HTTP ทุกรายการ ลักษณะการทำงานนี้จะคงที่สำหรับการโต้ตอบกับแหล่งที่มา HTTP ทุกครั้ง

หากต้องการแนวทางที่ละเอียดยิ่งขึ้น คุณสามารถแทรกพฤติกรรมแบบทันท่วงทีโดยใช้ ResolvingDataSource ข้อมูลโค้ดต่อไปนี้แสดงวิธีแทรกส่วนหัวของคำขอก่อนที่จะโต้ตอบกับแหล่งที่มา HTTP

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time request headers.
    dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri))
  }

Java

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time request headers.
        dataSpec -> dataSpec.withRequestHeaders(getCustomHeaders(dataSpec.uri)));

นอกจากนี้ คุณยังใช้ ResolvingDataSource เพื่อแก้ไข URI แบบทันท่วงทีได้ด้วย ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

Kotlin

val dataSourceFactory: DataSource.Factory =
  ResolvingDataSource.Factory(httpDataSourceFactory) { dataSpec: DataSpec ->
    // Provide just-in-time URI resolution logic.
    dataSpec.withUri(resolveUri(dataSpec.uri))
  }

Java

DataSource.Factory dataSourceFactory =
    new ResolvingDataSource.Factory(
        httpDataSourceFactory,
        // Provide just-in-time URI resolution logic.
        dataSpec -> dataSpec.withUri(resolveUri(dataSpec.uri)));

การปรับแต่งการจัดการข้อผิดพลาด

การใช้ LoadErrorHandlingPolicy ที่กำหนดเองช่วยให้แอปปรับแต่งวิธีที่ ExoPlayer ตอบสนองต่อข้อผิดพลาดในการโหลดได้ ตัวอย่างเช่น แอปอาจต้องการให้ดำเนินการไม่สำเร็จอย่างรวดเร็วแทนที่จะลองใหม่หลายครั้ง หรืออาจต้องการปรับแต่งตรรกะ Back-off ที่ควบคุมระยะเวลาที่ผู้เล่นต้องรอก่อนที่จะลองใหม่แต่ละครั้ง ข้อมูลโค้ดต่อไปนี้แสดงวิธีใช้ตรรกะ Back-off ที่กำหนดเอง

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(
      loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo
    ): Long {
      // Implement custom back-off logic here.
      return 0
    }
  }
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(
      DefaultMediaSourceFactory(context).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
    )
    .build()

Java

LoadErrorHandlingPolicy loadErrorHandlingPolicy =
    new DefaultLoadErrorHandlingPolicy() {
      @Override
      public long getRetryDelayMsFor(LoadErrorHandlingPolicy.LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(
            new DefaultMediaSourceFactory(context)
                .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy))
        .build();

อาร์กิวเมนต์ LoadErrorInfo มีข้อมูลเพิ่มเติมเกี่ยวกับการโหลดที่ไม่สำเร็จเพื่อปรับแต่งตรรกะตามประเภทข้อผิดพลาดหรือคำขอที่ไม่สำเร็จ

การปรับแต่งแฟล็กของเครื่องมือแยกข้อมูล

คุณสามารถใช้แฟล็ก Extractor เพื่อปรับแต่งวิธีแยกรูปแบบแต่ละรายการจากสื่อแบบโปรเกรสซีฟ โดยสามารถตั้งค่าได้ใน DefaultExtractorsFactory ที่ระบุไว้ใน DefaultMediaSourceFactory ตัวอย่างต่อไปนี้จะส่ง Flag ที่เปิดใช้การกรอตามดัชนีสำหรับสตรีม MP3

Kotlin

val extractorsFactory =
  DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING)
val player =
  ExoPlayer.Builder(context)
    .setMediaSourceFactory(DefaultMediaSourceFactory(context, extractorsFactory))
    .build()

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setMp3ExtractorFlags(Mp3Extractor.FLAG_ENABLE_INDEX_SEEKING);

ExoPlayer player =
    new ExoPlayer.Builder(context)
        .setMediaSourceFactory(new DefaultMediaSourceFactory(context, extractorsFactory))
        .build();

การเปิดใช้การค้นหาอัตราบิตคงที่

สำหรับสตรีม MP3, ADTS และ AMR คุณสามารถเปิดใช้การกรอโดยประมาณได้โดยใช้สมมติฐานอัตราบิตคงที่ที่มีแฟล็ก FLAG_ENABLE_CONSTANT_BITRATE_SEEKING คุณตั้งค่าแฟล็กเหล่านี้สำหรับเครื่องมือแยกแต่ละรายการได้โดยใช้วิธีการ DefaultExtractorsFactory.setXyzExtractorFlags แต่ละรายการตามที่อธิบายไว้ก่อนหน้านี้ หากต้องการเปิดใช้การกรอแบบอัตราบิตคงที่สำหรับเครื่องมือแยกทั้งหมดที่รองรับ ให้ใช้ DefaultExtractorsFactory.setConstantBitrateSeekingEnabled

Kotlin

val extractorsFactory = DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)

Java

DefaultExtractorsFactory extractorsFactory =
    new DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true);

จากนั้นคุณสามารถแทรก ExtractorsFactory โดยใช้ DefaultMediaSourceFactory ตามที่อธิบายไว้สำหรับการปรับแต่งแฟล็กตัวแยกข้อมูลก่อนหน้านี้

การเปิดใช้การจัดคิวบัฟเฟอร์แบบอะซิงโครนัส

การจัดคิวบัฟเฟอร์แบบอะซิงโครนัสเป็นการปรับปรุงไปป์ไลน์การแสดงผลของ ExoPlayer ซึ่งจะเรียกใช้อินสแตนซ์ MediaCodec ในโหมดอะซิงโครนัส และใช้เทรดเพิ่มเติมเพื่อกำหนดเวลาการถอดรหัสและการแสดงผลข้อมูล การเปิดใช้จะช่วยลดเฟรมที่หายไปและเสียงขาดหาย

การจัดคิวบัฟเฟอร์แบบอะซิงโครนัสจะเปิดใช้โดยค่าเริ่มต้นในอุปกรณ์ที่ใช้ Android 12 (ระดับ API 31) ขึ้นไป และสามารถเปิดใช้ด้วยตนเองได้ตั้งแต่ Android 6.0 (ระดับ API 23) เป็นต้นไป พิจารณาเปิดใช้ฟีเจอร์นี้สำหรับอุปกรณ์บางเครื่องที่คุณพบเฟรมที่หายไปหรือเสียงขาดหาย โดยเฉพาะเมื่อเล่นเนื้อหาที่ได้รับการคุ้มครองโดย DRM หรือเนื้อหาที่มีอัตราเฟรมสูง

ในกรณีที่ง่ายที่สุด คุณต้องแทรก DefaultRenderersFactory ลงในเพลเยอร์ดังนี้

Kotlin

val renderersFactory =
  DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing()
val exoPlayer = ExoPlayer.Builder(context, renderersFactory).build()

Java

DefaultRenderersFactory renderersFactory =
    new DefaultRenderersFactory(context).forceEnableMediaCodecAsynchronousQueueing();
ExoPlayer exoPlayer = new ExoPlayer.Builder(context, renderersFactory).build();

หากคุณสร้างอินสแตนซ์ของโปรแกรมแสดงผลโดยตรง ให้ส่ง new DefaultMediaCodecAdapter.Factory(context).forceEnableAsynchronous() ไปยังคอนสตรักเตอร์ MediaCodecVideoRenderer และ MediaCodecAudioRenderer

การปรับแต่งการดำเนินการด้วย ForwardingSimpleBasePlayer

คุณสามารถปรับแต่งลักษณะการทำงานบางอย่างของอินสแตนซ์ Player ได้โดยรวมไว้ในคลาสย่อยของ ForwardingSimpleBasePlayer คลาสนี้ช่วยให้คุณสกัดกั้น "การดำเนินการ" ที่เฉพาะเจาะจงได้ แทนที่จะต้องใช้เมธอด Player โดยตรง ซึ่งจะช่วยให้ลักษณะการทำงานของ play(), pause() และ setPlayWhenReady(boolean) เป็นไปในทิศทางเดียวกัน นอกจากนี้ยังช่วยให้มั่นใจว่าการเปลี่ยนแปลงสถานะทั้งหมดจะได้รับการเผยแพร่อย่างถูกต้องไปยังอินสแตนซ์ Player.Listener ที่ลงทะเบียนไว้ สำหรับ Use Case การปรับแต่งส่วนใหญ่ ควรใช้ ForwardingSimpleBasePlayer มากกว่า ForwardingPlayer ซึ่งมีแนวโน้มที่จะเกิดข้อผิดพลาดมากกว่า เนื่องจากมีการรับประกันความสอดคล้องเหล่านี้

เช่น หากต้องการเพิ่มตรรกะที่กำหนดเองเมื่อเริ่มหรือหยุดเล่น ให้ทำดังนี้

Kotlin

class PlayerWithCustomPlay(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun handleSetPlayWhenReady(playWhenReady: Boolean): ListenableFuture<*> {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady)
  }
}

Java

public static final class PlayerWithCustomPlay extends ForwardingSimpleBasePlayer {

  public PlayerWithCustomPlay(Player player) {
    super(player);
  }

  @Override
  protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
    // Add custom logic
    return super.handleSetPlayWhenReady(playWhenReady);
  }
}

หรือหากต้องการไม่อนุญาตคำสั่ง SEEK_TO_NEXT (และตรวจสอบว่า Player.seekToNext เป็นการดำเนินการที่ไม่มีผล):

Kotlin

class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun getState(): State {
    val state = super.getState()
    return state
      .buildUpon()
      .setAvailableCommands(
        state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()
      )
      .build()
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

Java

public static final class PlayerWithoutSeekToNext extends ForwardingSimpleBasePlayer {

  public PlayerWithoutSeekToNext(Player player) {
    super(player);
  }

  @Override
  protected State getState() {
    State state = super.getState();
    return state
        .buildUpon()
        .setAvailableCommands(
            state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build())
        .build();
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

การปรับแต่ง MediaSource

ตัวอย่างก่อนหน้านี้จะแทรกคอมโพเนนต์ที่กำหนดเองเพื่อใช้ระหว่างการเล่นออบเจ็กต์ MediaItem ทั้งหมดที่ส่งไปยังเพลเยอร์ ในกรณีที่จำเป็นต้องมีการปรับแต่งแบบละเอียด คุณยังสามารถแทรกคอมโพเนนต์ที่กำหนดเองลงในอินสแตนซ์ MediaSource แต่ละรายการ ซึ่งสามารถส่งไปยังเพลเยอร์ได้โดยตรง ตัวอย่างต่อไปนี้แสดงวิธีปรับแต่ง ProgressiveMediaSource เพื่อใช้ DataSource.Factory, ExtractorsFactory และ LoadErrorHandlingPolicy ที่กำหนดเอง

Kotlin

val mediaSource =
  ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
    .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
    .createMediaSource(MediaItem.fromUri(streamUri))

Java

ProgressiveMediaSource mediaSource =
    new ProgressiveMediaSource.Factory(customDataSourceFactory, customExtractorsFactory)
        .setLoadErrorHandlingPolicy(customLoadErrorHandlingPolicy)
        .createMediaSource(MediaItem.fromUri(streamUri));

การสร้างคอมโพเนนต์ที่กำหนดเอง

ไลบรารีจะให้การใช้งานเริ่มต้นของคอมโพเนนต์ที่แสดงไว้ที่ด้านบนของหน้านี้สำหรับ Use Case ทั่วไป ExoPlayer สามารถใช้คอมโพเนนต์เหล่านี้ได้ แต่อาจสร้างขึ้นเพื่อใช้การติดตั้งใช้งานที่กำหนดเองหากจำเป็นต้องใช้ลักษณะการทำงานที่ไม่ได้มาตรฐาน กรณีการใช้งานบางส่วนสำหรับการติดตั้งใช้งานที่กำหนดเองมีดังนี้

  • Renderer – คุณอาจต้องการใช้ Renderer ที่กำหนดเองเพื่อจัดการประเภทสื่อที่การติดตั้งใช้งานเริ่มต้นที่ไลบรารีมีให้ไม่รองรับ
  • TrackSelector – การใช้ TrackSelector ที่กำหนดเองช่วยให้นักพัฒนาแอปสามารถเปลี่ยนวิธีเลือกแทร็กที่แสดงโดย MediaSource เพื่อให้ Renderer แต่ละรายการที่พร้อมใช้งานนำไปใช้ได้
  • LoadControl – การใช้ LoadControl ที่กำหนดเองช่วยให้นักพัฒนาแอปเปลี่ยนนโยบายการบัฟเฟอร์ของเพลเยอร์ได้
  • Extractor – หากต้องการรองรับรูปแบบคอนเทนเนอร์ที่ไลบรารียังไม่รองรับในปัจจุบัน ให้พิจารณาใช้คลาส Extractor ที่กำหนดเอง
  • MediaSource – การใช้คลาส MediaSource ที่กำหนดเองอาจเหมาะสมหากคุณต้องการรับตัวอย่างสื่อเพื่อป้อนให้กับโปรแกรมแสดงผลในลักษณะที่กำหนดเอง หรือหากคุณต้องการใช้ลักษณะการคอมโพสิต MediaSource ที่กำหนดเอง
  • MediaSource.Factory – การใช้ MediaSource.Factory ที่กำหนดเองช่วยให้แอปพลิเคชันปรับแต่งวิธีสร้าง MediaSource จาก MediaItem ได้
  • DataSource – แพ็กเกจอัปสตรีมของ ExoPlayer มีการติดตั้งใช้งาน DataSource จำนวนหนึ่งสำหรับ Use Case ต่างๆ อยู่แล้ว คุณอาจต้องการใช้คลาส DataSource ของคุณเองเพื่อโหลดข้อมูลด้วยวิธีอื่น เช่น ผ่านโปรโตคอลที่กำหนดเอง โดยใช้สแต็ก HTTP ที่กำหนดเอง หรือจากแคชถาวรที่กำหนดเอง

เมื่อสร้างคอมโพเนนต์ที่กำหนดเอง เราขอแนะนำให้ทำดังนี้

  • หากคอมโพเนนต์ที่กำหนดเองจำเป็นต้องรายงานเหตุการณ์กลับไปยังแอป เราขอแนะนำให้คุณดำเนินการโดยใช้โมเดลเดียวกับคอมโพเนนต์ ExoPlayer ที่มีอยู่ เช่น ใช้คลาส EventDispatcher หรือส่ง Handler ร่วมกับ Listener ไปยังคอนสตรัคเตอร์ของคอมโพเนนต์
  • เราขอแนะนำให้คอมโพเนนต์ที่กำหนดเองใช้โมเดลเดียวกับคอมโพเนนต์ ExoPlayer ที่มีอยู่เพื่อให้แอปกำหนดค่าใหม่ได้ในระหว่างการเล่น ในการดำเนินการนี้ คอมโพเนนต์ที่กำหนดเองควรใช้ PlayerMessage.Target และรับการเปลี่ยนแปลงการกำหนดค่าในเมธอด handleMessage โค้ดของแอปพลิเคชันควรส่งการเปลี่ยนแปลงการกำหนดค่าโดยเรียกใช้เมธอด createMessage ของ ExoPlayer, กำหนดค่าข้อความ และส่งไปยังคอมโพเนนต์โดยใช้ PlayerMessage.send การส่งข้อความที่จะแสดงในชุดข้อความการเล่นช่วยให้มั่นใจได้ว่าข้อความจะทำงานตามลำดับกับการดำเนินการอื่นๆ ที่ทำในโปรแกรมเล่น