Free AI web copilot to create summaries, insights and extended knowledge, download it at here
3318
Abstract
ride all three methods if they do not make sense for your use case. So what is the responsibility of each method?</p><p id="2c91"><b>getSeekPositions()</b> — is a list of positions where thumbnails can be shown. Think of this as a timeline of discrete moments where a thumbnail can capture the current scene in the video. Similar concept to scene selection for DVDs but much more fine grained.</p><p id="6670"><b>getThumbnail(int, ResultCallback)</b> — this method contains your magic secret sauce for retrieving those moments. We will dive more into the details in a bit.</p><p id="867f"><b>reset()</b> — this is a hook to notify you to release resources, clear cache, or perform any other cleanup that your implementation requires.</p><figure id="9f26"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/0*DPt8Na6VzPwrZT1h."><figcaption></figcaption></figure><h1 id="75a3">Implementation</h1><p id="e5f5">Talking about an API is easy, but understanding how to create your implementation makes a lot more sense once you see an example.</p><p id="6b2a">Using <a href="https://developer.android.com/reference/android/media/MediaMetadataRetriever.html">MediaMetadataRetriever</a> to help us extract the thumbnails, the implementation becomes almost trivial. Note that this requires your video stream to have more metadata embedded into it. You do not have to use MediaMetadataRetriever, there are many ways to get a bitmap. I just used it for the sake of this example.</p><p id="744f">The first thing we would want to do is calculate our seek positions. Having a determined interval makes this a simple math problem. If you keep track of a timeline in your metadata, then there is no need for this extra math, just pass the along the timeline of seek positions.</p>
<figure id="0d08">
<div>
<div>
<iframe class="gist-iframe" src="/gist/benbaxter/679581ab2adcba8409549a318870b25f.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="5b25">Now that we have calculated our seek positions, our first method to implement becomes a simple getter method:</p><div id="f46f"><pre>@Override
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-built_in">long</span>[] <span class="hljs-title">getSeekPositions</span>()</span> {
<span class="hljs-keyword">return</span> mSeekPositions;
}</pre></div><p id="5a81">The bulk of the magic happens in the <i>getThumbnail()</i> method. We receive an index which corresponds to the a position in our seek positions array. The intent is that leanback gives us an index as a reference marker. We can combine that reference marker with our seek positions to get the time for the thumbnail.</p>
<figure id="13fb">
<div>
<div>
<iframe class="gist-iframe" src="/gist/benbaxter/b4e670aceea60ecba43a1ec7b1a03823.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="ceea">Why don’t we return anything from <i>getThumbnail()</i> and why are are we passed a callback? This pattern
Options
suggests an asynchronous architecture.</p><p id="f944"><i>getThumbnail()</i> is called on the UI thread so it’s best to offload the bulk of the work to the background. In this example I use an AsyncTask. <b>Note</b> that <i>retriever.getFrameAtTime()</i> is really slow. In a real world app, the video thumbnails usually are preprocessed in the cloud and downloaded when playing.</p>
<figure id="3002">
<div>
<div>
<iframe class="gist-iframe" src="/gist/benbaxter/69cf49f369e6ff46c53bcf6d1ea31bf0.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="4578">We now need to manage the AsyncTask’s lifecycle appropriately.</p><p id="ac00">If we implement the <i>reset()</i> method, we can use that to release our running threads. First we need to capture our async tasks when we start them; store them in a <a href="https://developer.android.com/reference/android/util/SparseArray.html">SparseArray</a>, list, or some collection. Since the image will be the same for each index, we only need one task per index. Thus, a given index maps to one seek position, one thumbnail, and one AsyncTask.</p>
<figure id="63ac">
<div>
<div>
<iframe class="gist-iframe" src="/gist/benbaxter/9db8dd73f34fe034a1cd65b12c4e3951.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="2fa7">Then in our <i>reset()</i> method, we should cancel and release the tasks.</p>
<figure id="2d6e">
<div>
<div>
<iframe class="gist-iframe" src="/gist/benbaxter/c1949d847ed2b5e46b3ac7dc52014519.js" allowfullscreen="" frameborder="0" height="undefined" width="undefined">
</div>
</div>
</figure></iframe></div></div></figure><p id="f158">We can apply this pattern for caching the thumbnails with an <a href="https://developer.android.com/reference/android/util/LruCache.html">LruCache</a> among other optimizations.</p><p id="5bd2">In summary, we have three methods to do whatever we need. As long as we understand each’s purpose, everything falls quietly into place.</p><p id="46f9">getSeekPositions() — controls our index range</p><p id="a6fa">getThumbnail() — magic happens</p><p id="ef60">reset() — releases resources</p><h1 id="d4e2">Continue learning</h1><p id="436b">I recommend reading the source code for <a href="https://github.com/googlesamples/leanback-showcase/blob/master/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/PlaybackSeekAsyncDataProvider.java">PlaybackSeekAsyncDataProvider</a> which demonstrates caching and prefetching; along with <a href="https://github.com/googlesamples/leanback-showcase/blob/master/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/PlaybackSeekDiskDataProvider.java">PlaybackSeekDiskDataProvider</a>.</p><p id="05e0">If you would like to join the discussion, leave a response or talk to me on <a href="https://twitter.com/benjamintravels">twitter</a>.</p></article></body>