Back to Repositories

Testing DASH Download and Offline Playback in SmartTube

This test suite validates DASH content downloading and playback functionality in ExoPlayer. It ensures proper handling of downloaded DASH media streams, cache management, and playback verification using the ExoPlayer framework.

Test Coverage Overview

The test suite provides comprehensive coverage of DASH download functionality:

  • Download validation of H264 video and AAC audio streams
  • Cache storage verification and cleanup
  • Playback testing of downloaded content
  • Stream key management and manifest parsing

Implementation Analysis

The testing approach utilizes JUnit4 with AndroidJUnit4 runner for Android-specific test execution. The implementation follows a structured setup-execute-verify pattern, using ExoPlayer’s DashDownloader and cache management components.

Key patterns include:
  • Temporary cache storage management
  • Custom data source factory configuration
  • Manifest parsing and stream selection

Technical Details

Testing tools and configuration:

  • ExoPlayer DashDownloader and DashUtil
  • SimpleCache for content caching
  • DefaultHttpDataSourceFactory for network operations
  • CacheDataSourceFactory for offline playback
  • ActivityTestRule for Android test environment

Best Practices Demonstrated

The test implementation showcases several testing best practices:

  • Proper resource cleanup in tearDown()
  • Isolated test environment using temporary storage
  • Clear separation of download and playback verification
  • Comprehensive assertion messages
  • Structured test setup with clear configuration options

yuliskov/smarttube

exoplayer-amzn-2.10.6/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashDownloadTest.java

            
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.android.exoplayer2.playbacktests.gts;

import static com.google.common.truth.Truth.assertWithMessage;

import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.ActivityTestRule;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.dash.DashUtil;
import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
import com.google.android.exoplayer2.source.dash.manifest.DashManifest;
import com.google.android.exoplayer2.source.dash.manifest.Representation;
import com.google.android.exoplayer2.source.dash.offline.DashDownloader;
import com.google.android.exoplayer2.testutil.HostActivity;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

/** Tests downloaded DASH playbacks. */
@RunWith(AndroidJUnit4.class)
public final class DashDownloadTest {

  private static final String TAG = "DashDownloadTest";

  private static final Uri MANIFEST_URI = Uri.parse(DashTestData.H264_MANIFEST);

  @Rule public ActivityTestRule<HostActivity> testRule = new ActivityTestRule<>(HostActivity.class);

  private DashTestRunner testRunner;
  private File tempFolder;
  private SimpleCache cache;
  private DefaultHttpDataSourceFactory httpDataSourceFactory;
  private CacheDataSourceFactory offlineDataSourceFactory;

  @Before
  public void setUp() throws Exception {
    testRunner =
        new DashTestRunner(TAG, testRule.getActivity())
            .setManifestUrl(DashTestData.H264_MANIFEST)
            .setFullPlaybackNoSeeking(true)
            .setCanIncludeAdditionalVideoFormats(false)
            .setAudioVideoFormats(
                DashTestData.AAC_AUDIO_REPRESENTATION_ID, DashTestData.H264_CDD_FIXED);
    tempFolder = Util.createTempDirectory(testRule.getActivity(), "ExoPlayerTest");
    cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
    httpDataSourceFactory = new DefaultHttpDataSourceFactory("ExoPlayer", null);
    offlineDataSourceFactory =
        new CacheDataSourceFactory(
            cache, DummyDataSource.FACTORY, CacheDataSource.FLAG_BLOCK_ON_CACHE);
  }

  @After
  public void tearDown() {
    testRunner = null;
    Util.recursiveDelete(tempFolder);
    cache = null;
  }

  // Download tests

  @Test
  public void testDownload() throws Exception {
    DashDownloader dashDownloader = downloadContent();
    dashDownloader.download(/* progressListener= */ null);

    testRunner
        .setStreamName("test_h264_fixed_download")
        .setDataSourceFactory(offlineDataSourceFactory)
        .run();

    dashDownloader.remove();

    assertWithMessage("There should be no cache key left").that(cache.getKeys()).isEmpty();
    assertWithMessage("There should be no content left").that(cache.getCacheSpace()).isEqualTo(0);
  }

  private DashDownloader downloadContent() throws Exception {
    DashManifest dashManifest =
        DashUtil.loadManifest(httpDataSourceFactory.createDataSource(), MANIFEST_URI);
    ArrayList<StreamKey> keys = new ArrayList<>();
    for (int pIndex = 0; pIndex < dashManifest.getPeriodCount(); pIndex++) {
      List<AdaptationSet> adaptationSets = dashManifest.getPeriod(pIndex).adaptationSets;
      for (int aIndex = 0; aIndex < adaptationSets.size(); aIndex++) {
        AdaptationSet adaptationSet = adaptationSets.get(aIndex);
        List<Representation> representations = adaptationSet.representations;
        for (int rIndex = 0; rIndex < representations.size(); rIndex++) {
          String id = representations.get(rIndex).format.id;
          if (DashTestData.AAC_AUDIO_REPRESENTATION_ID.equals(id)
              || DashTestData.H264_CDD_FIXED.equals(id)) {
            keys.add(new StreamKey(pIndex, aIndex, rIndex));
          }
        }
      }
    }
    DownloaderConstructorHelper constructorHelper =
        new DownloaderConstructorHelper(cache, httpDataSourceFactory);
    return new DashDownloader(MANIFEST_URI, keys, constructorHelper);
  }

}