कस्टमाइज़ेशन

ExoPlayer लाइब्रेरी के मुख्य हिस्से में Player इंटरफ़ेस होता है. Player, मीडिया प्लेयर के पारंपरिक हाई-लेवल फ़ंक्शन दिखाता है. जैसे, मीडिया को बफ़र करना, चलाना, रोकना, और आगे-पीछे करना. डिफ़ॉल्ट तौर पर लागू होने वाले ExoPlayer को, चलाए जा रहे मीडिया के टाइप, उसे कैसे और कहां सेव किया जाता है, और उसे कैसे रेंडर किया जाता है, इनके बारे में कुछ अनुमान लगाने के लिए डिज़ाइन किया गया है. इसलिए, इस पर कुछ पाबंदियां भी लगाई जाती हैं. मीडिया को सीधे लोड और रेंडर करने के बजाय, ExoPlayer लागू करने की सुविधा, यह काम उन कॉम्पोनेंट को सौंपती है जिन्हें प्लेयर बनाने या प्लेयर को नए मीडिया सोर्स पास करने पर इंजेक्ट किया जाता है. ExoPlayer लागू करने के लिए, ये कॉम्पोनेंट ज़रूरी हैं:

  • MediaSource इंस्टेंस, चलाए जाने वाले मीडिया की जानकारी देते हैं, मीडिया को लोड करते हैं, और लोड किए गए मीडिया को पढ़ते हैं. प्लेयर में मौजूद MediaSource.Factory, MediaItem से MediaSource इंस्टेंस बनाता है. मीडिया सोर्स पर आधारित प्लेलिस्ट एपीआई का इस्तेमाल करके, इन्हें सीधे प्लेयर को भी भेजा जा सकता है.
  • MediaSource.Factory का ऐसा इंस्टेंस जो MediaItem को MediaSource में बदलता है. प्लेयर बनाने पर, MediaSource.Factory को इंजेक्ट किया जाता है.
  • Renderer ऐसे इंस्टेंस जो मीडिया के अलग-अलग कॉम्पोनेंट को रेंडर करते हैं. ये तब इंजेक्ट किए जाते हैं, जब प्लेयर बनाया जाता है.
  • ऐसा TrackSelector जो MediaSource से मिले ट्रैक चुनता है, ताकि हर उपलब्ध Renderer का इस्तेमाल किया जा सके. प्लेयर बनाने पर, TrackSelector इंजेक्ट किया जाता है.
  • LoadControl, जो यह कंट्रोल करता है कि MediaSource कब ज़्यादा मीडिया को बफ़र करे और कितने मीडिया को बफ़र करे. खिलाड़ी बनाने पर, LoadControl इंजेक्ट किया जाता है.
  • एक LivePlaybackSpeedControl, जो लाइव प्लेबैक के दौरान प्लेबैक स्पीड को कंट्रोल करता है, ताकि प्लेयर को कॉन्फ़िगर किए गए लाइव ऑफ़सेट के करीब रखा जा सके. प्लेयर बनाने पर, LivePlaybackSpeedControl इंजेक्ट किया जाता है.

लाइब्रेरी में, प्लेयर की सुविधाओं को लागू करने वाले कॉम्पोनेंट इंजेक्ट करने का कॉन्सेप्ट मौजूद है. कुछ कॉम्पोनेंट के डिफ़ॉल्ट तौर पर लागू होने पर, वे इंजेक्ट किए गए अन्य कॉम्पोनेंट को काम सौंप देते हैं. इससे कई सब-कॉम्पोनेंट को, कस्टम तरीके से कॉन्फ़िगर किए गए लागू करने के तरीकों से अलग-अलग बदला जा सकता है.

प्लेयर को पसंद के मुताबिक बनाना

कॉम्पोनेंट इंजेक्ट करके प्लेयर को पसंद के मुताबिक बनाने के कुछ सामान्य उदाहरणों के बारे में यहां बताया गया है.

नेटवर्क स्टैक को कॉन्फ़िगर करना

हमारे पास ExoPlayer के इस्तेमाल किए जाने वाले नेटवर्क स्टैक को पसंद के मुताबिक बनाने के बारे में एक पेज है.

नेटवर्क से लोड किए गए डेटा को कैश मेमोरी में सेव करना

इस्तेमाल के दौरान कुछ समय के लिए कैश मेमोरी में सेव करने और मीडिया डाउनलोड करने के बारे में गाइड देखें.

सर्वर इंटरैक्शन को पसंद के मुताबिक बनाना

कुछ ऐप्लिकेशन, एचटीटीपी अनुरोधों और उनके जवाबों को इंटरसेप्ट करना चाहते हैं. हो सकता है कि आप कस्टम अनुरोध हेडर इंजेक्ट करना चाहें, सर्वर के रिस्पॉन्स हेडर पढ़ें, अनुरोधों के यूआरआई में बदलाव करें वगैरह. उदाहरण के लिए, मीडिया सेगमेंट का अनुरोध करते समय, आपका ऐप्लिकेशन हेडर के तौर पर टोकन इंजेक्ट करके अपनी पुष्टि कर सकता है.

यहां दिए गए उदाहरण में, DefaultMediaSourceFactory में कस्टम DataSource.Factory को इंजेक्ट करके, इन व्यवहारों को लागू करने का तरीका बताया गया है:

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" शामिल होता है. एचटीटीपी सोर्स के साथ हर इंटरैक्शन के लिए, यह व्यवहार पहले से तय होता है.

ज़्यादा जानकारी वाले तरीके के लिए, ResolvingDataSource का इस्तेमाल करके, 'जस्ट-इन-टाइम' व्यवहार को इंजेक्ट किया जा सकता है. नीचे दिए गए कोड स्निपेट में, एचटीटीपी सोर्स के साथ इंटरैक्ट करने से ठीक पहले, अनुरोध हेडर इंजेक्ट करने का तरीका बताया गया है:

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 का इस्तेमाल भी किया जा सकता है. इस बारे में यहां दिए गए स्निपेट में बताया गया है:

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 को कैसे जवाब देना है. उदाहरण के लिए, हो सकता है कि कोई ऐप्लिकेशन कई बार कोशिश करने के बजाय, तुरंत फ़ेल हो जाए. इसके अलावा, हो सकता है कि वह बैक-ऑफ़ लॉजिक को पसंद के मुताबिक बनाना चाहे. यह लॉजिक यह कंट्रोल करता है कि हर बार कोशिश करने के बीच प्लेयर कितनी देर इंतज़ार करता है. नीचे दिए गए स्निपेट में, कस्टम बैक-ऑफ़ लॉजिक को लागू करने का तरीका बताया गया है:

Kotlin

val loadErrorHandlingPolicy: LoadErrorHandlingPolicy =
  object : DefaultLoadErrorHandlingPolicy() {
    override fun getRetryDelayMsFor(loadErrorInfo: 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(LoadErrorInfo loadErrorInfo) {
        // Implement custom back-off logic here.
        return 0;
      }
    };

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

LoadErrorInfo आर्ग्युमेंट में, लोड न हो पाने के बारे में ज़्यादा जानकारी होती है, ताकि गड़बड़ी के टाइप या अनुरोध पूरा न होने के आधार पर लॉजिक को पसंद के मुताबिक बनाया जा सके.

एक्सट्रैक्टर फ़्लैग को पसंद के मुताबिक बनाना

एक्सट्रैक्टर फ़्लैग का इस्तेमाल करके, यह तय किया जा सकता है कि प्रोग्रेसिव मीडिया से अलग-अलग फ़ॉर्मैट कैसे निकाले जाएं. इन्हें DefaultMediaSourceFactory के लिए दिए गए DefaultExtractorsFactory पर सेट किया जा सकता है. नीचे दिया गया उदाहरण एक फ़्लैग पास करता है, जो एमपी3 स्ट्रीम के लिए इंडेक्स के आधार पर सर्च करने की सुविधा चालू करता है.

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 (एपीआई लेवल 31) और इसके बाद के वर्शन वाले डिवाइसों पर डिफ़ॉल्ट रूप से चालू होती है. साथ ही, Android 6.0 (एपीआई लेवल 23) से इसे मैन्युअल तरीके से चालू किया जा सकता है. जिन डिवाइसों पर फ़्रेम या ऑडियो के रुकने की समस्या आ रही है उन पर यह सुविधा चालू करें. ऐसा खास तौर पर, डीआरएम (डिजिटल राइट्स मैनेजमेंट) से सुरक्षित या ज़्यादा फ़्रेम रेट वाला कॉन्टेंट चलाते समय करें.

सबसे आसान तरीके से, आपको प्लेयर में 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();

अगर रेंडरर को सीधे इंस्टैंशिएट किया जा रहा है, तो MediaCodecVideoRenderer और MediaCodecAudioRenderer कन्स्ट्रक्टर को एक AsynchronousMediaCodecAdapter.Factory पास करें.

ForwardingSimpleBasePlayer की मदद से कार्रवाइयों को पसंद के मुताबिक बनाना

Player इंस्टेंस के कुछ व्यवहार को पसंद के मुताबिक बनाया जा सकता है. इसके लिए, उसे ForwardingSimpleBasePlayer के किसी सबक्लास में रैप करें. इस क्लास की मदद से, सीधे Player तरीकों को लागू करने के बजाय, खास 'ऑपरेशन' को इंटरसेप्ट किया जा सकता है. इससे यह पक्का होता है कि play(), pause() और setPlayWhenReady(boolean) एक जैसे काम करते हैं. इससे यह भी पक्का होता है कि स्टेटस में हुए सभी बदलाव, रजिस्टर किए गए Player.Listener इंस्टेंस में सही तरीके से लागू हों. ज़्यादातर कस्टमाइज़ेशन के इस्तेमाल के उदाहरणों के लिए, ForwardingSimpleBasePlayer को ForwardingPlayer के मुकाबले प्राथमिकता दी जानी चाहिए. ऐसा इसलिए, क्योंकि ForwardingSimpleBasePlayer में एक जैसी परफ़ॉर्मेंस मिलती है और गड़बड़ी की संभावना कम होती है.

उदाहरण के लिए, वीडियो चलाने या रोकने पर कोई कस्टम लॉजिक जोड़ने के लिए:

Kotlin

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

Java

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

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));

कस्टम कॉम्पोनेंट बनाना

लाइब्रेरी, इस्तेमाल के सामान्य उदाहरणों के लिए, इस पेज पर सबसे ऊपर दिए गए कॉम्पोनेंट को डिफ़ॉल्ट रूप से लागू करती है. ExoPlayer इन कॉम्पोनेंट का इस्तेमाल कर सकता है. हालांकि, अगर स्टैंडर्ड व्यवहार की ज़रूरत नहीं है, तो कस्टम तरीके से लागू करने के लिए भी इसे बनाया जा सकता है. कस्टम लागू करने के कुछ उदाहरण यहां दिए गए हैं:

  • Renderer – लाइब्रेरी से डिफ़ॉल्ट रूप से लागू होने वाले तरीकों के साथ काम न करने वाले मीडिया टाइप को मैनेज करने के लिए, कस्टम Renderer लागू किया जा सकता है.
  • TrackSelector – कस्टम TrackSelector लागू करने से, ऐप्लिकेशन डेवलपर को MediaSource से एक्सपोज़ किए गए ट्रैक को, उपलब्ध हर Renderer के लिए इस्तेमाल करने के तरीके में बदलाव करने की अनुमति मिलती है.
  • LoadControl – कस्टम LoadControl लागू करने पर, ऐप्लिकेशन डेवलपर को प्लेयर की बफ़रिंग नीति बदलने की अनुमति मिलती है.
  • Extractor – अगर आपको किसी ऐसे कंटेनर फ़ॉर्मैट के साथ काम करना है जो फ़िलहाल लाइब्रेरी के साथ काम नहीं करता, तो कस्टम Extractor क्लास लागू करें.
  • MediaSource – अगर आपको रेंडरर को पसंद के मुताबिक फ़ीड करने के लिए मीडिया सैंपल चाहिए या आपको पसंद के मुताबिक MediaSource कंपोजिट करने का तरीका लागू करना है, तो कस्टम MediaSource क्लास लागू करना सही रहेगा.
  • MediaSource.Factory – कस्टम MediaSource.Factory लागू करने पर, ऐप्लिकेशन को MediaItem से MediaSource बनाने के तरीके को पसंद के मुताबिक बनाने की अनुमति मिलती है.
  • DataSource – ExoPlayer के अपस्ट्रीम पैकेज में, इस्तेमाल के अलग-अलग उदाहरणों के लिए, पहले से ही कई DataSource लागू हैं. हो सकता है कि आप डेटा को किसी दूसरे तरीके से लोड करने के लिए, अपनी DataSource क्लास लागू करना चाहें. जैसे, कस्टम प्रोटोकॉल पर, कस्टम एचटीटीपी स्टैक का इस्तेमाल करके या कस्टम पर्सिस्टेंट कैश मेमोरी से.

कस्टम कॉम्पोनेंट बनाते समय, हमारा सुझाव है कि:

  • अगर किसी कस्टम कॉम्पोनेंट को ऐप्लिकेशन में इवेंट की रिपोर्ट भेजनी है, तो हमारा सुझाव है कि आप मौजूदा ExoPlayer कॉम्पोनेंट के जैसे ही मॉडल का इस्तेमाल करें. उदाहरण के लिए, EventDispatcher क्लास का इस्तेमाल करना या कॉम्पोनेंट के कन्स्ट्रक्टर में, लिसनर के साथ Handler को पास करना.
  • हमारा सुझाव है कि कस्टम कॉम्पोनेंट, मौजूदा ExoPlayer कॉम्पोनेंट के जैसे ही मॉडल का इस्तेमाल करें. इससे, वीडियो चलाने के दौरान ऐप्लिकेशन को कॉन्फ़िगर करने की अनुमति मिलती है. ऐसा करने के लिए, कस्टम कॉम्पोनेंट को PlayerMessage.Target लागू करना चाहिए और handleMessage तरीके में कॉन्फ़िगरेशन में बदलाव पाना चाहिए. ऐप्लिकेशन कोड को, कॉन्फ़िगरेशन में किए गए बदलावों को पास करना चाहिए. इसके लिए, उसे ExoPlayer के createMessage तरीके को कॉल करना होगा. साथ ही, मैसेज को कॉन्फ़िगर करना होगा और PlayerMessage.send का इस्तेमाल करके उसे कॉम्पोनेंट को भेजना होगा. प्लेलिस्ट थ्रेड पर डिलीवर किए जाने वाले मैसेज भेजने से यह पक्का होता है कि वे प्लेयर पर किए जा रहे अन्य ऑपरेशन के क्रम में ही लागू किए जाएं.