Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement full offline support for DASH/HLS/SS/Misc #2643

Closed
ghost opened this issue Apr 4, 2017 · 61 comments
Closed

Implement full offline support for DASH/HLS/SS/Misc #2643

ghost opened this issue Apr 4, 2017 · 61 comments
Assignees

Comments

@ghost
Copy link

ghost commented Apr 4, 2017

Hi,

Is download and/or progressive download of adaptive streaming presentations a feature that is planned for ExoPlayer?

Note I am referring to 'up front' acquisition of the content rather than local HTTP caching.

Thanks.

@erdemguven
Copy link
Contributor

Hi @kidmiracleman, yes we're working on DASH downloading currently. It should be published in a few weeks. HLS and SmoothStreaming support will follow.

@ghost
Copy link
Author

ghost commented Apr 4, 2017

@erdemguven Thank you for the update. Looking forward to the release...

@ojw28 ojw28 changed the title [Question] Content Downloading / Progressive download with ExoPlayer Implement full offline support for DASH/HLS/SS/Misc Apr 4, 2017
@ojw28
Copy link
Contributor

ojw28 commented Apr 4, 2017

Let's use this as an overall tracking issue for offline support.

@ram992
Copy link

ram992 commented Apr 9, 2017

So, are you going to release any offline donwload manager kind of thing

@workspace
Copy link

Can you give me a hint as to what interface you are providing to show the progress of the download?

@erdemguven
Copy link
Contributor

@ram992 yes there will be a download manager.

@KiminRyu There will be a listener interface and broadcast events depending on the download method you use. int totalSegments, int downloadedSegments, long downloadedBytes are provided but this might change.

@ram992
Copy link

ram992 commented Apr 10, 2017

Wow, but I had to implement all these things on my own,
but thanks for inducing such incredible features, might help other fresh exo guys to use them.

Can you say anything on when to expect the download Manager. Casue I want to use that.

@erdemguven
Copy link
Contributor

@ram992 Sorry I can't as it's hard to estimate at the moment.

@bnussey
Copy link

bnussey commented Apr 11, 2017

I've recently configured this on iOS, you may of already seen it, but it could be a good resource for you to reference - https://developer.apple.com/library/content/documentation/AudioVideo/Conceptual/MediaPlaybackGuide/Contents/Resources/en.lproj/HTTPLiveStreaming/HTTPLiveStreaming.html

@ram992
Copy link

ram992 commented Apr 12, 2017

sure no problem,

Can you make a comment once it is available in dev branch

@workspace
Copy link

@erdemguven Is this commit to meet the dependency? I want to know how roughly it is completed!

42e4100

@ojw28
Copy link
Contributor

ojw28 commented Apr 24, 2017

No. We will update this issue when significant progress is made. You may see some commits that look odd from time to time, where the commit message and actual content of the commit don't align well. This includes the one you reference. This is a side effect of the way we use internal tools for development work + mirror to GitHub.

@bnussey
Copy link

bnussey commented Jun 20, 2017

Any updates on this @ojw28 ? Just deciding if we need to code this ourselves or if we should wait

@mkiisoft
Copy link

mkiisoft commented Jul 25, 2017

I'll keep tracking this issue. Once implemented, I'll create a demo and full "doc" so everyone can use it with full offline mode and cache using DASH or similar.

@hishamMuneer
Copy link

@mkiisoft @ojw28
I need this feature in my app, can you guys provide us some info by when can we get the offline mode?

@ram992
Copy link

ram992 commented Jul 31, 2017

@hishamMuneer I think you can do this yourself if you need it for DASH content, as in the latest exoplayer there is a class OfflineLicenseHelper to handle license issues

@hishamMuneer
Copy link

@ram992 I dont have licence issues, for now I am trying to download the video for offline viewing, even if it is not protected by licence. A single mp4 file can be downloaded easily but what about a segmented dashed file. https://stackoverflow.com/questions/45418817/saving-a-video-offline-while-playing-in-exoplayer

@ram992
Copy link

ram992 commented Aug 1, 2017

@hishamMuneer One question : why do you want to use a segmented DASH for offline use,
This stuff is for online and to switch bitrate dynamically (depending on the network speed).
And a single mp4 video file is enough for offline case as there will be any network calls.
Any way the problem with DASH formatt (as I know) is that it will have all the audio and video files with in the manifest and if you want to play the segmented one you need to have all of them available in some form(online/offline).
So make a call. Download all the mp4 files or simply ask the user what quality he wants and download them.

@hishamMuneer
Copy link

@ram992 But that will double my server size as I have to store both full mp4 and segmented files of the same video. Right now i have segmented files (along with mpd) stored on server which i am able to play without any problem.

@ram992
Copy link

ram992 commented Aug 1, 2017

I know, I faced that problem, but I don't know any other way. That is how the DASH formatt works.
I hope someone from google can help us in this matter I guess. And I think all other famous stream apps does the same thing.

@hishamMuneer
Copy link

@mkiisoft @ojw28 Please guide how should I proceed? "Right now i have segmented files (along with mpd) stored on server which i am able to play without any problem. But how segmented videos running through mpd files can be saved offline for later use. Videos are unencrypted for now and no DRM is being used."

@erdemguven
Copy link
Contributor

@hishamMuneer We're planning to release some of the downloaders in August. We'll update this thread when it's done. You'll be able to download your DASH streams.

@mraj0045
Copy link

mraj0045 commented Sep 12, 2017

@ojw28 I have Downloaded the Dash stream to the local file using the DashDownloader as mentioned in the above comment. But I don't know how to use the cached data to play content.

I have tried using ExtractorMediaSource like below:

new ExtractorMediaSource(
    uri,
    new CacheDataSourceFactory(
        cache, getHttpDataSourceFactory(false), CacheDataSource.FLAG_BLOCK_ON_CACHE),
    new DefaultExtractorsFactory(),
    null,
    null);

and also DashMediaSource:

new DashMediaSource(
         uri,
         new CacheDataSourceFactory(
             cache, getHttpDataSourceFactory(false), CacheDataSource.FLAG_BLOCK_ON_CACHE),
         new DefaultDashChunkSource.Factory(getDataSourceFactory(false)),
         null,
         null);

Neither of the implementation is playing and also don't know whether above implementation is correct or not.
It would be helpful if you tell me how to use the CacheDataSource to play the video Offline.

Thanks in advance.

@bnussey
Copy link

bnussey commented Sep 12, 2017

Hey @mraj0045 have you tried using instructions from @q-litzler above

@mraj0045
Copy link

mraj0045 commented Sep 13, 2017

@bnussey, @erdemguven I tried using the instructions you pointed out for the Sample URL: http://yt-dash-mse-test.commondatastorage.googleapis.com/media/feelings_vp9-20130806-manifest.mpd. It is working perfectly.

But in my application, it is not working. It is throwing,

I/ExoPlayerImpl: Init bb98758 [ExoPlayerLib/2.5.2] [lux_uds, XT1562, motorola, 23]
D/EventLogger: state [0.00, true, I]
D/EventLogger: state [0.09, true, B]
E/EventLogger: internalError [0.18, loadError]
E/EventLogger: internalError [0.18, loadError]
D/OpenGLRenderer: endAllActiveAnimators on 0xb8b759d8 (RippleDrawable) with handle 0xb877b5c8
E/EventLogger: internalError [1.07, loadError]
E/EventLogger: internalError [3.08, loadError]
E/ExoPlayerImplInternal: Source error.
E/EventLogger: playerFailed [3.08]
D/EventLogger: state [3.08, true, I]

The difference with the Sample URLand my URL is,
Sample URL not DRM protected and mine is DRM protected.
Sample URL contains multiple video formats only and mine have both multiple video and audio URL.

My Code:

private DataSource.Factory getDataSourceFactory(boolean useBandwidthMeter) {
    File file = Utility.getOfflineDir(this, "test");
    SimpleCache cache = new SimpleCache(file, new NoOpCacheEvictor());
    return new CacheDataSourceFactory(
        cache, new DefaultDataSourceFactory(this, userAgent), CacheDataSource.FLAG_BLOCK_ON_CACHE);
  }

  private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
    int type =
        TextUtils.isEmpty(overrideExtension)
            ? Util.inferContentType(uri)
            : Util.inferContentType("." + overrideExtension);
    switch (type) {
      case C.TYPE_DASH:
        return new DashMediaSource(
            uri,
            mediaDataSourceFactory,
            new DefaultDashChunkSource.Factory(getDataSourceFactory(false)),
            mHandler,
            eventLogger);
      case C.TYPE_OTHER:
        return new ExtractorMediaSource(
            uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), mHandler, eventLogger);
      default:
        {
          throw new IllegalStateException("Unsupported type: " + type);
        }
    }
  }

  public DefaultRenderersFactory getRenderersFactory() {
    UUID drmSchemeUuid = C.WIDEVINE_UUID;
    if (drmSchemeUuid != null) {
      //      PersistentCookieStore persistentCookieStore = new PersistentCookieStore(this);
      //      List<HttpCookie> cookies = persistentCookieStore.getCookies();
      //      HashMap<String, String> keyRequestProperties;
      //      if (cookies.isEmpty()) {
      //        keyRequestProperties = null;
      //      } else {
      //        keyRequestProperties = new HashMap<>();
      //        String phpSession = "";
      //        for (HttpCookie cookie : cookies) {
      //          phpSession = cookie.getName() + "=" + cookie.getValue();
      //          Log.e("Session", phpSession);
      //        }
      //        keyRequestProperties.put("Cookie", phpSession);
      //      }

      //            boolean preferExtensionDecoders = false;
      @DefaultRenderersFactory.ExtensionRendererMode
      int extensionRendererMode = DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER;
      try {
        DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
        drmSessionManager =
            buildDrmSessionManager(drmSchemeUuid, "https://widevine-proxy.appspot.com/proxy", null);
        return new DefaultRenderersFactory(this, drmSessionManager, extensionRendererMode);
      } catch (UnsupportedDrmException | DrmSession.DrmSessionException e) {
        e.printStackTrace();
      }
    }
    return new DefaultRenderersFactory(this);
  }

  private DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(
      UUID uuid, String licenseUrl, HashMap<String, String> keyRequestPropertiesArray)
      throws UnsupportedDrmException, DrmSession.DrmSessionException {

    HttpMediaDrmCallback drmCallback =
        new HttpMediaDrmCallback(licenseUrl, getHttpDataSourceFactory(false));
    if (keyRequestPropertiesArray != null)
      for (Map.Entry<String, String> entry : keyRequestPropertiesArray.entrySet()) {
        drmCallback.setKeyRequestProperty(entry.getKey(), entry.getValue());
      }

    drmSessionManager =
        new DefaultDrmSessionManager<>(
            uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback, null, mHandler, this);
    drmSessionManager.setMode(
        DefaultDrmSessionManager.MODE_QUERY, Utility.decode("a3NpZEIxNkE5MTU1"));
    //    Utility.decode("a3NpZEY0MDc0MzBE")
    //    offlineLicenseHelper.downloadLicense()
    return drmSessionManager;
  }

Need help in implementing it. Thanks in advance.

@workspace
Copy link

@ojw28 Do you mean 'DownloadManager' is from android framework? or own implementation on this library?

@sim0nG
Copy link

sim0nG commented Nov 3, 2017

@mraj0045 could you please share how did you manage to use the CacheDataSource to play the video Offline? I am constructing DashMediaSource after I have downloaded it this way:

new DashMediaSource(Uri.parse(DASH_TEST_URL), getDataSourceFactory(false), 
                       new DefaultDashChunkSource.Factory(getDataSourceFactory(false)), mainHandler, 
                         eventLogger);

private DataSource.Factory getDataSourceFactory(boolean useBandwidthMeter) {
        return new CacheDataSourceFactory(
                cache, buildDataSourceFactory(useBandwidthMeter), 0);
    }

 private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
        return buildDataSourceFactory(useBandwidthMeter ? BANDWIDTH_METER : null);
    }

private DataSource.Factory buildDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
        return new DefaultDataSourceFactory(this, bandwidthMeter,
                buildHttpDataSourceFactory(bandwidthMeter));
    }

private HttpDataSource.Factory buildHttpDataSourceFactory(DefaultBandwidthMeter bandwidthMeter) {
        return new DefaultHttpDataSourceFactory(userAgent, bandwidthMeter);
    }

where cache is the one I used to download previously.

@dhanashreeborkar
Copy link

How to download DASH DRM video on custom button click instead of caching it while playing?

1.How DRM enabled video are downloaded using exoplayer methods?
2.Where do we store and maintain the list downloaded videos?

@rabindrachhachan
Copy link

Hi all,
Can someone share/refer code for downloading video and playing the same video in offline mode. I went through the demo and able to play the contents online. But I want to learn how to download the contents and play offline. My video url will be a MP4 file , no constraints of license . Some guidance will be appreciated . Thanks in advanced.

@andrewlewis
Copy link
Collaborator

@rabindrachhachan You may find Erdem's blog post on pre-caching/downloading progressive streams useful.

andrewlewis pushed a commit that referenced this issue Feb 8, 2018
Issue: #2643

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=184844484
@FD-
Copy link

FD- commented Feb 20, 2018

Hi all,
I highly appreciate the effort you put into developing this great library, but I'm a bit confused on how to use the offline functionality:

  • What is the difference between the CacheUtils and the *Downloader (eg HlsDownloader) approach?
  • For the *Downloaders, how exactly can I pick a specific representation (eg stream quality) for download? A few lines of sample code would be great!

Thanks in advance!

@erdemguven
Copy link
Contributor

@FD- CacheUtils can be used to download any single content, ProgressiveDownloader uses it to download progressive streams. To download adaptive streams, you need to use Downloaders which handles multiple segments in the stream automatically. You can also use DownloadService for background downloads. DownloadService uses DownloadManager which handles multiple downloads and continue download when the app restarts. It's also possible to configure the downloads to be run only when there is wifi or the device is charging. As you can see Downloaders are for more complex cases.
A demo app showing how to use DownloadService will be released in a few weeks.

@FD-
Copy link

FD- commented Feb 21, 2018

@erdemguven Thanks for the clarification! I managed to implement caching for HLS and DASH. For selecting a subset of available qualities I use HlsPlaylistParser and DashManifestParser to parse the manifests, then select qualities. For later only playing the cached qualities, I use custom parsers that filter the manifest using the copy() function. Works great!

One thing I'd love to see supported is downloading parts of live streams. I've created a new feature request for that (#3877).

@erdemguven
Copy link
Contributor

Please see my Downloading Streams blog post on Medium.

@workspace
Copy link

workspace commented Apr 27, 2018

I tried to integrate new offline feature with locally imported ExoPlayer. Do you have any plan to improve download speed? The average download speed is measured as 1MB(or lower) per second. Dash Downloader's implementation is designed to receive chunks sequentially so that downloads are slow even if the internet speed is fast. Although simultaneous downloads are possible (currently set to 4 in consideration of background service restrictions), this does not seem to improve user experience. It took 40 minutes to download a 50 minute 720p video. The total downloaded bytes are 700MB. It is clear that it takes 40 minutes to receive 700MB to be improved. I used Giga WiFi and Galaxy s9 Plus as download environment.

@kvillnv
Copy link

kvillnv commented Apr 27, 2018

@erdemguven thanks for the post on Medium.

-Any updates about Live stream download?
If I'm not mistaken, it is going to be handled by this class: ProgressiveDownloader.java ? it seems method download() is not yet fully implemented to prepare downloaders for the next non yet existing segments.

-And could you define "Progressive Stream"? are you referring to a live Streaming with segments that do not exist yet? (I could only find clear definitions for "Streaming" and "Progressive Download")

@ojw28
Copy link
Contributor

ojw28 commented May 1, 2018

@erdemguven is out on holiday at the moment. To answer your questions:

Any updates about Live stream download?

No, sorry

If I'm not mistaken, it is going to be handled by this class: ProgressiveDownloader.java ? it seems method download() is not yet fully implemented to prepare downloaders for the next non yet existing segments. And could you define "Progressive Stream"? are you referring to a live Streaming with segments that do not exist yet? (I could only find clear definitions for "Streaming" and "Progressive Download")

This is not correct. ProgressDownloader is just for any regular media file (i.e. not DASH, SmoothStreaming or HLS). For example an mp3 file.

@kvillnv
Copy link

kvillnv commented May 2, 2018

ok, thanks @ojw28

@KiminRyu Maybe that can help you: I had the same download issue, it was extremly slow, and actually it was not related to the DashDownloader or the connection speed! I just replaced my external storage by a faster one and I just downloaded 1GB in 4min

@workspace
Copy link

@kvillnv
Thank you for answer. I also dig up a lot about this issue, but it seems like our media cdn is a little slow. Have you ever done a remove action? This is separate from the network, and the manifest I'm using for testing has about 2,000 to 3,000 chunks. As Cache erased the chunks one by one, it took quite a while to delete them. I hope it will improve in this part as well.

@kvillnv
Copy link

kvillnv commented May 8, 2018

@ojw28 @erdemguven is there any way to limit the bandwidth in the download service?
If I need to play a stream while I am downloading, on small connections it might be an issue.

@KiminRyu yes I have done remove actions, it is not instantaneous but it's not that long.

@kvillnv
Copy link

kvillnv commented May 9, 2018

It seems that when the cache becomes voluminous it becomes slower to start playback:
(I only downloaded Dash streams, with only 1 audio and 1 video representation)
When I have for example 2 medias or less already downloaded in the cache, I play one of them, it starts instantly, no problem.
But when I have more, my player shows a black screen for a few seconds before starting. The more medias in cache the longer it is. Up to 45 seconds, when I have a dozen of medias.

Here is how I instantiate the cache and MediaSource for reading:

val upstreamDataSourceFactory = DefaultDataSourceFactory(this, userAgent)
cacheFile = RecorderService.getCacheFile(this)
val cacheDataSourceFactory = CacheDataSourceFactory(cacheFile,upstreamDataSourceFactory)

return DashMediaSource.Factory(
              DefaultDashChunkSource.Factory(cacheDataSourceFactory), cacheDataSourceFactory)
                .setManifestParser(FilteringManifestParser(parser, keys))
                .createMediaSource(mediaUri)

Here is my method to get the cacheFile:

fun getCacheFile(context: Context) = SimpleCache(File(getMediaFolder(context), CACHE_FOLDER), NoOpCacheEvictor())

On the CacheDataSourceFactory I also tried to use CacheDataSource.FLAG_BLOCK_ON_CACHE and to set the maxBytes, I didn't notice a difference

Everything is working full offline (DRM and Media)

Any suggestions?
Thanks

@workspace
Copy link

workspace commented May 9, 2018

@kvillnv How about follow this way to initiate CacheDataSourceFactory? It is from the latest release(2.8.0)'s demo application. It works fine even I downloaded more than 20 dash streams. uhmm...and I think you will get SimpleCache Error(it should be singleton over application scope. It is updated just a couple of days ago) if you get SimpleCache like that. I recommend to provide your simple cache in application scope by dagger or something.

val appComponent = (application as ECApplication).component()
    val downloadCache = if (useExternal) appComponent.externalCache() else appComponent.internalCache()
    val component: MediaDownloadComponent = DaggerMediaDownloadComponent.builder()
        .mediaDownloadModule(MediaDownloadModule(this, downloadCache, useExternal))
        .build()
    component.inject(this)

Here is my example for injecting application scope SimpleCache to MediaDownloadService(which I implement DownloadService!)

@kvillnv
Copy link

kvillnv commented May 10, 2018

@KiminRyu thank you, problem solved by reviewing my SimpleCache

@erdemguven
Copy link
Contributor

erdemguven commented May 11, 2018

@ojw28 @erdemguven is there any way to limit the bandwidth in the download service?
If I need to play a stream while I am downloading, on small connections it might be an issue.

@kvillnv, please look at PriorityTaskManager. If you create a PriorityTaskManager and use it with your DefaultLoadControl and DownloadManager (you should pass it DownloaderConstructorHelper and use it while contructing DownloadManager), it will make downloads pause while player is buffering.

Closing this issue as now we support downloading adaptive streams. Please open separate issue for future problems.

@SikanderZeb
Copy link

SikanderZeb commented May 28, 2018

@erdemguven is there any support to download and offline play protected DRM content? I've been trying the Exoplayer2 recent demo. Added my urls to the assets, but on download I got This demo app does not support downloading protected content. However I made some changes in the example and managed to download videos, but when I try to play the downloaded videos, the player shows video duration, video play progress is also moving but there is no audio and video.

05-28 16:45:32.436 9006-9204/sonmobile.schoolofnet.com W/AudioCapabilities: Unsupported mime audio/ape 05-28 16:45:32.438 9006-9204/sonmobile.schoolofnet.com W/AudioCapabilities: Unsupported mime audio/x-adpcm-ms 05-28 16:45:32.439 9006-9204/sonmobile.schoolofnet.com W/AudioCapabilities: Unsupported mime audio/x-adpcm-dvi-ima 05-28 16:45:32.445 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unrecognized profile/level 1/32 for video/mp4v-es 05-28 16:45:32.445 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unrecognized profile/level 32768/2 for video/mp4v-es 05-28 16:45:32.445 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unrecognized profile/level 32768/64 for video/mp4v-es 05-28 16:45:32.458 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unsupported mime video/x-ms-wmv 05-28 16:45:32.462 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unsupported mime video/divx 05-28 16:45:32.465 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unsupported mime video/divx3 05-28 16:45:32.469 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unsupported mime video/xvid 05-28 16:45:32.472 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unsupported mime video/flv1 05-28 16:45:32.475 9006-9204/sonmobile.schoolofnet.com W/VideoCapabilities: Unrecognized profile/level 1/32 for video/mp4v-es 05-28 16:45:32.528 9006-9204/sonmobile.schoolofnet.com I/VideoCapabilities: Unsupported profile 4 for video/mp4v-es

@erdemguven
Copy link
Contributor

To be able to play offline DRM protected content you need to download licenses too. You can use OfflineLicenseHelper to download the licenses. There can be multiple licenses. To keep the demo app simple we didn't deliberately disabled downloading DRM content.
If this answer isn't enough please open a new issue with all information asked in the template.

@google google locked as resolved and limited conversation to collaborators May 28, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests