Google’s ExoPlayer is best in class media player for Android and it supports many features which are not currently supported by stock MediaPlayer.
Best part about Exo is it’s OpenSource-ness 🙂
In this series of blog posts I’m gonna walk through a couple of useful hacks/customisations on some of Exo components:
- Stats For Nerds – Part 1
- Improved Buffering – Part 2
Find the demo on github which covers both features.
Stats for Nerds:
With Adaptive streaming techniques(HLS, Smooth, DASH), playback is heavily relied on connection speed, meaning resolution changes to adapt to fluctuations in bandwidth which means data consumption varies with time.
As a geek I’ve wondered what’s my n/w consumption, conn speed & sometimes buffer length when I stream a video online. Youtube on Chrome answered these concerns with ‘Stats For Nerd’ mode which depicts all those stats using simple dynamic charts and I found it intriguing. So I decided to do something similar on ExoPlayer and turned out it’s easier than I thought.
Which Stats?
- Connection Speed:
- Network Activity
- Buffer Health
- Dropped Frames
- Video & Screen Resolutions
Code Please
- Connection speed & n/w activity can be obtained by passing BandwidthMeter.EventListener while creating DefaultBandwidthMeter object.
bandwidthMeter = new DefaultBandwidthMeter(mUiUpdateHandler, new BandwidthMeter.EventListener() { @Override public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) { bitrateEstimate = bitrate; bytesDownloaded = bytes; } });
- ExoPlayer exposes all available Tracks and their corresponding Formats and setting a Video debug listener will give update you whenever input video format changes. Format is a container of all meta data related to a Video Rendition (width, height, bitrate etc.)
- Dropped frames can be obtained by Video debug listener too.
player.setVideoDebugListener(new VideoRendererEventListener() { .... @Override public void onVideoInputFormatChanged(Format format) { currentVideoFormat = format; } @Override public void onDroppedFrames(int count, long elapsedMs) { droppedFrames += count; } ...... }
Buffer health & LoadControl
LoadControl is an Exo Component interface to control buffering of Media and there is a DefaultLoadControl in SDK which takes of when to start buffering and for how long.
We can create our version of DefaultLoadControl with an event listener & a handler to notify player on a buffer data. Check CustomLoadControl class for more info.
player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, new CustomLoadControl(new CustomLoadControl.EventListener() { @Override public void onBufferedDurationSample(long bufferedDurationUs) { bufferedDurationMs = bufferedDurationUs; } }, mUiUpdateHandler)); player.addListener(this);
OK. How to draw those awesome charts?
MPAndroidChart is a second to none charting library in Android and it covers all our charting requirements with no exceptions.
Create a Handler to repetitively call to itself every 500ms to update speed & buffer data and N/W activity has to be updated atleast with 1100ms so that updates wont overlap.
private void startPlayerStats() { mUiUpdateHandler.removeMessages(MSG_UPDATE_STATS); mUiUpdateHandler.removeMessages(MSG_UPDATE_STATS_NW_ONLY); depictPlayerStats(); depictPlayerNWStats(); } protected void depictPlayerStats() { if (!canShowStats()) return; String buffer = DemoUtil.getFormattedDouble((bufferedDurationMs / Math.pow(10, 6)), 1); String brEstimate = DemoUtil.getFormattedDouble((bitrateEstimate / Math.pow(10, 3)), 1); updateStatChart(player_stats_health_chart, Float.parseFloat(buffer), ColorTemplate.getHoloBlue(), "Buffer Health: " + buffer + " s"); updateStatChart(player_stats_speed_chart, Float.parseFloat(brEstimate), Color.LTGRAY, "Conn Speed: " + DemoUtil.humanReadableByteCount( bitrateEstimate, true, true) + "ps"); player_stats_size.setText("Screen Dimensions: " + simpleExoPlayerView.getWidth() + " x " + simpleExoPlayerView.getHeight()); player_stats_res.setText("Video Resolution: " + (null != currentVideoFormat ? (currentVideoFormat.width + " x " + currentVideoFormat.height) : "NA")); player_stats_dropframes.setText("Dropped Frames: " + droppedFrames); mUiUpdateHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATS, 500); } protected void depictPlayerNWStats() { if (!canShowStats()) return; updateStatChart(player_stats_nw_chart, (float) (bytesDownloaded / Math.pow(10, 3)), Color.CYAN, "Network Activity: " + DemoUtil.humanReadableByteCount( bytesDownloaded, true)); bytesDownloaded = 0; mUiUpdateHandler.sendEmptyMessageDelayed(MSG_UPDATE_STATS_NW_ONLY, 1100); }
Follow my next blog in series for Part 2 – Improved Buffering.