google / ExoPlayer

This project is deprecated and stale. The latest ExoPlayer code is available in https://github.com/androidx/media
https://developer.android.com/media/media3/exoplayer
Apache License 2.0
21.7k stars 6.02k forks source link

Cronet extension: Support cookie storage #5975

Open stufan opened 5 years ago

stufan commented 5 years ago

I am trying to switch streaming from HttpDataSource to CronetDataSource. The way I am doing it by adding official ExoPlayer cronet extension: https://github.com/google/ExoPlayer/tree/release-v2/extensions/cronet

Then in code just change: DataSource.Factory mDataSourceFactory = new DefaultDataSourceFactory(mContext, RemoteGateway.getUserAgent(), bandwidthMeter);

to:

DataSource.Factory mDataSourceFactory = new DefaultDataSourceFactory(
                mContext,
                bandwidthMeter,
                new CronetDataSourceFactory(
                        new CronetEngineWrapper(mContext),
                        Executors.newSingleThreadExecutor(),
                        null,
                        bandwidthMeter,
                        RemoteGateway.getUserAgent()));

What happens is that the stream works without any issues when using HttpDataSource but fails with CronetDataSource. After debugging it turns out that CronetDataSource doesn't process Set-Cookie header(s) return by my streaming CDN (Amazon CloudFront) on master playlist response and doesn't pass those cookies on subsequent requests for AES key and/or chunklists.

I search for ExoPlayer documentation and wasn't able to find info how to correctly configure cookie management when Cronet extension is used.

Here is my ExoPlayer info: D/CronetEngineWrapper: CronetEngine built using App-Packaged-Cronet-Provider I/ExoPlayerImpl: Init ef5675b [ExoPlayerLib/2.10.1] [generic_x86, sdk_google_atv_x86, unknown, 25]

Thanks Stefan

ojw28 commented 5 years ago

Cronet doesn't store cookies, as per this issue. I think the best approach here would be to:

  1. Define a simple CookieStore interface in the Cronet extension, and allow an implementation to be injected from app code.
  2. App code would be responsible for injecting an implementation. We'd design CookieStore to trivially map onto OkHttp's CookieJar or the platform CookieHandler, so developers can easily implement CookieStore as a wrapper around one of these APIs. It would probably just define:
put(Uri uri, Map<String, List<String>> responseHeaders);
Map<String, List<String>> get(Uri);

Comment 4 on the issue above has a few changed references that show how easy the mapping is, and how the interface would be used from inside CronetDataSource.

We wont be treating this as high priority, but would accept a pull request if you're able to help ;).

drayan85 commented 3 years ago

I have used this additional method to add the Cookie Header every time for the existing httpDataSourceFactory manually and works for me.

private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory;

  public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(final Context context) {
    if (httpDataSourceFactory == null) {
      CronetEngineWrapper cronetEngineWrapper = new CronetEngineWrapper(context.getApplicationContext(),
              USER_AGENT, /* preferGMSCoreCronet= */ false);
      httpDataSourceFactory = new CronetDataSource.Factory(cronetEngineWrapper, Executors.newSingleThreadExecutor())
              .setHandleSetCookieRequests(true).setAllowCrossProtocolRedirects(true);
    }
    return httpDataSourceFactory;
  }
  public static void addCronetCookies(final Context context, final HashMap<String, String> cookies) {
    if (httpDataSourceFactory == null) {
      getHttpDataSourceFactory(context);
    }
    StringBuilder builder = new StringBuilder();
    for (String key : cookies.keySet()) {
      if (builder.length() > 0) {
        builder.append(";");
      }
      builder.append(key).append("=").append(cookies.get(key));
    }
    HashMap<String, String> headerCookie = new HashMap<>();
    headerCookie.put("Cookie", builder.toString());

    httpDataSourceFactory.setDefaultRequestProperties(headerCookie);
  }