Analytics

প্লেব্যাক অ্যানালিটিক্স সংক্রান্ত বিভিন্ন ধরনের প্রয়োজনীয়তা পূরণে ExoPlayer সাহায্য করে। মোটকথা, প্লেব্যাক থেকে ডেটা সংগ্রহ, ব্যাখ্যা, একত্রিত ও সারসংক্ষেপ করা ই হল অ্যানালিটিক্স। এই ডেটা ডিভাইসে ব্যবহার করা যেতে পারে—যেমন, লগিং, ডিবাগিং বা ভবিষ্যতের প্লেব্যাক সংক্রান্ত সিদ্ধান্ত নেওয়ার জন্য—অথবা সব ডিভাইস জুড়ে প্লেব্যাক মনিটর করতে কোনও সার্ভারে রিপোর্ট করা যেতে পারে।

কোনও অ্যানালিটিক্স সিস্টেমকে সাধারণত প্রথমে ইভেন্ট সংগ্রহ করতে হয় এবং তারপরে সেগুলিকে আরও প্রসেস করে অর্থপূর্ণ করে তুলতে হয়:

  • ইভেন্ট সংগ্রহ করা: ExoPlayer ইনস্ট্যান্সে AnalyticsListener রেজিস্টার করার মাধ্যমে এটি করা যেতে পারে। প্লেয়ার ব্যবহারের সময় ইভেন্ট ঘটলে রেজিস্টার করা অ্যানালিটিক্স লিসনাররা সেগুলি পান। প্রতিটি ইভেন্ট প্লেলিস্টের সংশ্লিষ্ট মিডিয়া আইটেমের সাথে যুক্ত থাকে, সেই সাথে প্লেব্যাক পজিশন ও টাইমস্ট্যাম্প মেটাডেটাও থাকে।
  • ইভেন্ট প্রসেসিং: কিছু অ্যানালিটিক্স সিস্টেম, সব ইভেন্ট প্রসেসিং সার্ভার-সাইডে পারফর্ম করা সহ, একটি সার্ভারে র ইভেন্ট আপলোড করে। এছাড়াও, ডিভাইসে ইভেন্ট প্রসেস করা সম্ভব এবং এটি করলে, আপলোড করতে হবে এমন তথ্যের পরিমাণ কমে যেতে পারে। ExoPlayer PlaybackStatsListener প্রদান করে, যা আপনাকে নিম্নলিখিত প্রসেসিং ধাপগুলি সম্পাদন করতে দেয়:
    1. ইভেন্ট ইন্টারপ্রিটেশন: অ্যানালিটিক্সের উদ্দেশ্যে উপযোগী হতে, ইভেন্টকে সিঙ্গেল প্লেব্যাকের প্রসঙ্গে ইন্টারপ্রিট করতে হবে। যেমন, প্লেয়ারের স্টেট পরিবর্তন করে STATE_BUFFERING করার র ইভেন্টটি প্রাথমিক বাফারিং, আবার বাফারিং করা অথবা সিক করার পরে বাফারিং হওয়ার সাথে সম্পর্কিত হতে পারে।
    2. স্টেট ট্র্যাকিং: এই ধাপ ইভেন্টকে কাউন্টারে কনভার্ট করে। যেমন, স্টেট পরিবর্তন ইভেন্টকে এমন কাউন্টারে কনভার্ট করা যেতে পারে যা ট্র্যাক করে যে প্রতিটি প্লেব্যাক স্টেটে কত সময় ব্যয় করা হয়েছে। ফলাফল হল একটি প্লেব্যাকের জন্য অ্যানালিটিক্স ডেটা ভ্যালুর একটি প্রাথমিক সেট।
    3. এগ্রিগেশন: এই ধাপে একাধিক প্লেব্যাক জুড়ে অ্যানালিটিক্স ডেটা একত্রিত করা হয়, সাধারণত কাউন্টার যোগ করে।
    4. সারসংক্ষেপ মেট্রিকের গণনা: সবচেয়ে উপযোগী মেট্রিকের মধ্যে অনেকগুলিই এমন যেগুলি গড় হিসেব করে অথবা অন্য উপায়ে প্রাথমিক অ্যানালিটিক্স ডেটার ভ্যালু একত্রিত করে। একক বা একাধিক প্লেব্যাকের জন্য সারসংক্ষেপ মেট্রিক গণনা করা যেতে পারে।

AnalyticsListener-এর মাধ্যমে ইভেন্ট সংগ্রহ করা

প্লেয়ার থেকে পাওয়া কাঁচা প্লেব্যাক ইভেন্ট AnalyticsListener ইমপ্লিমেন্টেশনে রিপোর্ট করা হয়। আপনি সহজেই নিজের লিসনার যোগ করতে এবং শুধুমাত্র আপনার আগ্রহের মেথড ওভাররাইড করতে পারবেন:

Kotlin

exoPlayer.addAnalyticsListener(
  object : AnalyticsListener {
    override fun onPlaybackStateChanged(eventTime: EventTime, @Player.State state: Int) {}

    override fun onDroppedVideoFrames(
      eventTime: EventTime,
      droppedFrames: Int,
      elapsedMs: Long,
    ) {}
  }
)

জাভা

exoPlayer.addAnalyticsListener(
    new AnalyticsListener() {
      @Override
      public void onPlaybackStateChanged(EventTime eventTime, @Player.State int state) {}

      @Override
      public void onDroppedVideoFrames(
          EventTime eventTime, int droppedFrames, long elapsedMs) {}
    });

প্রতিটি কলব্যাকে পাস করা EventTime প্লেলিস্টের কোনও মিডিয়া আইটেমের সাথে ইভেন্টকে অ্যাসোসিয়েট করে, সেই সাথে প্লেব্যাক পজিশন ও টাইমস্ট্যাম্প মেটাডেটা:

  • realtimeMs: ইভেন্টের দেওয়াল ঘড়ির সময়।
  • timeline, windowIndex ও mediaPeriodId: প্লেলিস্ট ও প্লেলিস্টের মধ্যে থাকা সেই আইটেমকে সংজ্ঞায়িত করে যার সাথে ইভেন্টটি সম্পর্কিত। mediaPeriodId ঐচ্ছিক অতিরিক্ত তথ্য থাকে, যেমন, আইটেমের মধ্যে থাকা বিজ্ঞাপনের ইভেন্ট।
  • eventPlaybackPositionMs: ইভেন্টটি ঘটার সময় আইটেমের প্লেব্যাক পজিশন।
  • currentTimeline, currentWindowIndex, currentMediaPeriodId এবং currentPlaybackPositionMs: উপরে উল্লেখ করা হয়েছে, তবে বর্তমানে চালানো হচ্ছে এমন আইটেমের জন্য। বর্তমানে যে আইটেমটি চলছে সেটি সেই আইটেম থেকে আলাদা হতে পারে যার সাথে ইভেন্টটি সম্পর্কিত, যেমন, ইভেন্টটি যদি পরবর্তী আইটেম চালানোর আগে প্রি-বাফারিংয়ের সাথে সম্পর্কিত হয়।

PlaybackStatsListener-এর মাধ্যমে ইভেন্ট প্রসেসিং

PlaybackStatsListener হল একটি AnalyticsListener যা অন-ডিভাইস ইভেন্ট প্রসেসিং প্রয়োগ করে। এটি কাউন্টার ও প্রাপ্ত মেট্রিক সহ PlaybackStats গণনা করে, যার মধ্যে রয়েছে:

  • সারসংক্ষেপ মেট্রিক, যেমন মোট প্লেব্যাক সময়।
  • অ্যাডাপ্টিভ প্লেব্যাক কোয়ালিটি মেট্রিক, যেমন, গড় ভিডিও রেজোলিউশন।
  • রেন্ডারিং কোয়ালিটি মেট্রিক, যেমন ড্রপ করা ফ্রেমের হার।
  • রিসোর্স ব্যবহার সংক্রান্ত মেট্রিক, যেমন নেটওয়ার্কের মাধ্যমে কত বাইট ডেটা পড়া হয়েছে।

উপলভ্য গণনা ও প্রাপ্ত মেট্রিকের সম্পূর্ণ তালিকা আপনি PlaybackStats Javadoc-এ পাবেন।

PlaybackStatsListener প্লেলিস্টের প্রতিটি মিডিয়া আইটেমের জন্য আলাদা আলাদা PlaybackStats গণনা করে এবং এইসব আইটেমের মধ্যে যোগ করা প্রতিটি ক্লায়েন্ট-সাইড বিজ্ঞাপনও গণনা করে। প্লেব্যাক শেষ হয়ে গেলে সেই বিষয়ে জানতে PlaybackStatsListener-কে কলব্যাক প্রদান করতে পারেন এবং কোন প্লেব্যাক শেষ হয়েছে তা শনাক্ত করতে কলব্যাকে পাস করা EventTime ব্যবহার করতে পারেন। একাধিক প্লেব্যাকের জন্য অ্যানালিটিক্স ডেটা একত্রিত করা সম্ভব। এছাড়াও, PlaybackStats ব্যবহার করে যেকোনও সময় বর্তমান প্লেব্যাক সেশনের জন্য কোয়েরি করা সম্ভব। PlaybackStatsListener.getPlaybackStats()

Kotlin

exoPlayer.addAnalyticsListener(
  PlaybackStatsListener(/* keepHistory= */ true) {
    eventTime: EventTime?,
    playbackStats: PlaybackStats?
    -> // Analytics data for the session started at `eventTime` is ready.
  }
)

জাভা

exoPlayer.addAnalyticsListener(
    new PlaybackStatsListener(
        /* keepHistory= */ true,
        (eventTime, playbackStats) -> {
          // Analytics data for the session started at `eventTime` is ready.
        }));

PlaybackStatsListener-এর কনস্ট্রাক্টর প্রসেস করা ইভেন্টের সম্পূর্ণ ইতিহাস সেভ করে রাখার বিকল্প দেয়। মনে রাখবেন, প্লেব্যাকের দৈর্ঘ্য এবং ইভেন্টের সংখ্যার উপর নির্ভর করে এর ফলে অজানা মেমরি ওভারহেড হতে পারে। তাই, আপনাকে যদি শুধু চূড়ান্ত অ্যানালিটিক্স ডেটার পরিবর্তে প্রসেস করা ইভেন্টের সম্পূর্ণ ইতিহাস অ্যাক্সেস করতে হয়, তাহলেই এটি চালু করা উচিত।

মনে রাখবেন, PlaybackStats-এ মিডিয়ার অবস্থা ছাড়াও ব্যবহারকারীর প্লে করার উদ্দেশ্য এবং আরও বিস্তারিত তথ্য যেমন, প্লেব্যাক কেন বাধা পেয়েছে বা শেষ হয়েছে, তা বোঝাতে স্টেটের একটি বর্ধিত সেট ব্যবহার করা হয়:

প্লেব্যাক স্টেট খেলার ব্যাপারে ব্যবহারকারীর উদ্দেশ্য খেলার কোনও উদ্দেশ্য নেই
প্লেব্যাকের আগে JOINING_FOREGROUND NOT_STARTED, JOINING_BACKGROUND
অ্যাক্টিভ প্লেব্যাক PLAYING
প্লেব্যাক বাধা পেয়েছে BUFFERING, SEEKING PAUSED, PAUSED_BUFFERING, SUPPRESSED, SUPPRESSED_BUFFERING, INTERRUPTED_BY_AD
ফাইনাল স্টেট ENDED, STOPPED, FAILED, ABANDONED

ব্যবহারকারীর প্লে করার উদ্দেশ্য গুরুত্বপূর্ণ কারণ এর মাধ্যমে ব্যবহারকারী কখন প্লেব্যাক চালিয়ে যাওয়ার জন্য অ্যাক্টিভভাবে অপেক্ষা করছিলেন তা প্যাসিভ অপেক্ষা করার সময় থেকে আলাদা করা যায়। যেমন, PlaybackStats.getTotalWaitTimeMs, JOINING_FOREGROUND, BUFFERING এবং SEEKING স্টেটে মোট কত সময় ব্যয় করা হয়েছে তা রিটার্ন করে, কিন্তু প্লেব্যাক পজ করা হলে সেই সময় রিটার্ন করে না। একইভাবে, PlaybackStats.getTotalPlayAndWaitTimeMs ব্যবহারকারীর খেলার ইচ্ছার সাথে মোট সময় রিটার্ন করবে, অর্থাৎ মোট অ্যাক্টিভ অপেক্ষা করার সময় এবং PLAYING স্টেটে কাটানো মোট সময়।

প্রসেস ও ব্যাখ্যা করা ইভেন্ট

PlaybackStatsListener এর সাথে keepHistory=true ব্যবহার করে আপনি প্রসেস করা ও ব্যাখ্যা করা ইভেন্ট রেকর্ড করতে পারবেন। এর ফলে প্রাপ্ত PlaybackStats-এ নিম্নলিখিত ইভেন্ট তালিকা থাকবে:

  • playbackStateHistory: এক্সটেন্ডেড প্লেব্যাক স্টেটের একটি ক্রমিক তালিকা যার মধ্যে EventTime রয়েছে যেখান থেকে সেগুলি প্রয়োগ করা শুরু হয়েছে। এছাড়াও, আপনি PlaybackStats.getPlaybackStateAtTime ব্যবহার করে নির্দিষ্ট ওয়াল ঘড়ির সময়ে স্টেট দেখতে পারেন।
  • mediaTimeHistory: ওয়াল ক্লক টাইম ও মিডিয়া টাইমের পেয়ারের ইতিহাস যা আপনাকে মিডিয়ার কোন অংশ কোন সময়ে চালানো হয়েছিল তা রিকনস্ট্রাক্ট করতে দেয়। এছাড়াও, আপনি নির্দিষ্ট ওয়াল ক্লক টাইমে প্লেব্যাক পজিশন দেখতে PlaybackStats.getMediaTimeMsAtRealtimeMs ব্যবহার করতে পারবেন।
  • videoFormatHistory ও audioFormatHistory: প্লেব্যাকের সময় ব্যবহৃত ভিডিও ও অডিও ফর্ম্যাটের ক্রমিক তালিকা এবং EventTime যে সময়ে সেগুলি ব্যবহার করা শুরু হয়েছে।
  • fatalErrorHistory ও nonFatalErrorHistory: মারাত্মক ও অমারাত্মক সমস্যার ক্রমিক তালিকা, সাথে EventTime যেখানে সেগুলি হয়েছে। মারাত্মক সমস্যা হল সেগুলি যেগুলির কারণে প্লেব্যাক বন্ধ হয়ে গেছে, অন্যদিকে অমারাত্মক সমস্যা হয়ত সমাধান করা যেত।

সিঙ্গেল-প্লেব্যাক অ্যানালিটিক্স ডেটা

আপনি PlaybackStatsListener ব্যবহার করলে, এমনকি keepHistory=false চালু থাকলেও এই ডেটা অটোমেটিক সংগ্রহ করা হয়। ফাইনাল ভ্যালু হল পাবলিক ফিল্ড যা আপনি PlaybackStats Javadoc-এ দেখতে পাবেন এবং প্লেব্যাক স্টেট ডিউরেশন getPlaybackStateDurationMs-এর মাধ্যমে রিটার্ন করা হয়। সুবিধার জন্য, আপনি getTotalPlayTimeMs এবং getTotalWaitTimeMs-এর মতো মেথডও পাবেন যা নির্দিষ্ট প্লেব্যাক স্টেট কম্বিনেশনের সময়সীমা রিটার্ন করে।

Kotlin

Log.d(
  "DEBUG",
  "Playback summary: " +
    "play time = " +
    playbackStats.totalPlayTimeMs +
    ", rebuffers = " +
    playbackStats.totalRebufferCount,
)

জাভা

Log.d(
    "DEBUG",
    "Playback summary: "
        + "play time = "
        + playbackStats.getTotalPlayTimeMs()
        + ", rebuffers = "
        + playbackStats.totalRebufferCount);

একাধিক প্লেব্যাকের সামগ্রিক অ্যানালিটিক্স ডেটা

আপনি PlaybackStats.merge-এ কল করে একাধিক PlaybackStats একসাথে PlaybackStats.merge করতে পারবেন। এর ফলে PlaybackStats-এ মার্জ করা সব প্লেব্যাকের একত্রিত ডেটা থাকবে। মনে রাখবেন, এতে আলাদা আলাদা প্লেব্যাক ইভেন্টের ইতিহাস থাকবে না, কারণ এগুলি একত্রিত করা যায় না।

PlaybackStatsListener.getCombinedPlaybackStats ব্যবহার করে কোনও PlaybackStatsListener-এর লাইফটাইমে সংগ্রহ করা সব অ্যানালিটিক্স ডেটার সামগ্রিক ভিউ পাওয়া যেতে পারে।

গণনা করা সারসংক্ষেপ মেট্রিক

প্রাথমিক অ্যানালিটিক্স ডেটা ছাড়াও, PlaybackStats সারসংক্ষেপ মেট্রিক গণনা করার জন্য অনেক পদ্ধতি প্রদান করে।

Kotlin

Log.d(
  "DEBUG",
  "Additional calculated summary metrics: " +
    "average video bitrate = " +
    playbackStats.meanVideoFormatBitrate +
    ", mean time between rebuffers = " +
    playbackStats.meanTimeBetweenRebuffers,
)

জাভা

Log.d(
    "DEBUG",
    "Additional calculated summary metrics: "
        + "average video bitrate = "
        + playbackStats.getMeanVideoFormatBitrate()
        + ", mean time between rebuffers = "
        + playbackStats.getMeanTimeBetweenRebuffers());

উন্নত বিষয়

প্লেব্যাক মেটাডেটার সাথে অ্যানালিটিক্স ডেটা অ্যাসোসিয়েট করা

আলাদা আলাদা প্লেব্যাকের জন্য অ্যানালিটিক্স ডেটা সংগ্রহ করার সময়, আপনি হয়ত প্লে করা মিডিয়া সম্পর্কে মেটাডেটার সাথে প্লেব্যাক অ্যানালিটিক্স ডেটা অ্যাসোসিয়েট করতে চাইবেন।

MediaItem.Builder.setTag-এর মাধ্যমে মিডিয়া-নির্দিষ্ট মেটাডেটা সেট করলে ভাল হয়। মিডিয়া ট্যাগ EventTime কাঁচা ইভেন্টের জন্য রিপোর্ট করা হয় এবং সেগুলি PlaybackStats সম্পূর্ণ হয়ে গেলে, সংশ্লিষ্ট অ্যানালিটিক্স ডেটা হ্যান্ডেল করার সময় এটি সহজেই পাওয়া যায়:

Kotlin

PlaybackStatsListener(/* keepHistory= */ false) {
  eventTime: EventTime,
  playbackStats: PlaybackStats ->
  val mediaTag =
    eventTime.timeline
      .getWindow(eventTime.windowIndex, Timeline.Window())
      .mediaItem
      .localConfiguration
      ?.tag
  // Report playbackStats with mediaTag metadata.
}

জাভা

new PlaybackStatsListener(
    /* keepHistory= */ false,
    (eventTime, playbackStats) -> {
      Object mediaTag =
          eventTime.timeline.getWindow(eventTime.windowIndex, new Timeline.Window())
              .mediaItem
              .localConfiguration
              .tag;
      // Report playbackStats with mediaTag metadata.
    });

কাস্টম অ্যানালিটিক্স ইভেন্ট সম্পর্কে রিপোর্ট করা

অ্যানালিটিক্স ডেটাতে কাস্টম ইভেন্ট যোগ করার প্রয়োজন হলে, আপনাকে নিজের ডেটা স্ট্রাকচারে এইসব ইভেন্ট সেভ করতে হবে এবং পরে রিপোর্ট করা PlaybackStats-এর সাথে সেগুলি একত্রিত করতে হবে। সহায়তার জন্য, আপনি DefaultAnalyticsCollector আপনার কাস্টম ইভেন্টের জন্য EventTime ইনস্ট্যান্স তৈরি করতে এবং আগে থেকেই রেজিস্টার করা শ্রোতাদের কাছে সেগুলি পাঠাতে পারবেন, যেমনটি নিম্নলিখিত উদাহরণে দেখানো হয়েছে।

Kotlin

@OptIn(UnstableApi::class)
private interface ExtendedListener : AnalyticsListener {
  fun onCustomEvent(eventTime: EventTime)
}

@OptIn(UnstableApi::class)
private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) {

  fun customEvent() {
    val eventTime = super.generateCurrentPlayerMediaPeriodEventTime()
    super.sendEvent(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener ->
      if (listener is ExtendedListener) {
        listener.onCustomEvent(eventTime)
      }
    }
  }
}

@OptIn(UnstableApi::class)
fun useExtendedAnalyticsCollector(context: Context) {
  // Usage - Setup and listener registration.
  val player = ExoPlayer.Builder(context).setAnalyticsCollector(ExtendedCollector()).build()
  player.addAnalyticsListener(
    object : ExtendedListener {
      override fun onCustomEvent(eventTime: EventTime) {
        // Save custom event for analytics data.
      }
    }
  )
  // Usage - Triggering the custom event.
  (player.analyticsCollector as ExtendedCollector).customEvent()
}

জাভা

@OptIn(markerClass = UnstableApi.class)
private interface ExtendedListener extends AnalyticsListener {
  void onCustomEvent(EventTime eventTime);
}

@OptIn(markerClass = UnstableApi.class)
private static class ExtendedCollector extends DefaultAnalyticsCollector {
  public ExtendedCollector() {
    super(Clock.DEFAULT);
  }

  public void customEvent() {
    AnalyticsListener.EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
    sendEvent(
        eventTime,
        CUSTOM_EVENT_ID,
        listener -> {
          if (listener instanceof ExtendedListener) {
            ((ExtendedListener) listener).onCustomEvent(eventTime);
          }
        });
  }
}

@OptIn(markerClass = UnstableApi.class)
public static void useExtendedAnalyticsCollector(Context context) {
  // Usage - Setup and listener registration.
  ExoPlayer player =
      new ExoPlayer.Builder(context).setAnalyticsCollector(new ExtendedCollector()).build();
  player.addAnalyticsListener(
      (ExtendedListener)
          eventTime -> {
            // Save custom event for analytics data.
          });
  // Usage - Triggering the custom event.
  ((ExtendedCollector) player.getAnalyticsCollector()).customEvent();
}