Add GL demo app
Demonstrates rendering to a GLSurfaceView while applying a GL shader. Issue: #6920 PiperOrigin-RevId: 293551724
This commit is contained in:
parent
e3f43ee2ce
commit
0dd8e6a7c1
@ -42,6 +42,10 @@
|
|||||||
* UI
|
* UI
|
||||||
* move logic of prev, next, fast forward and rewind to ControlDispatcher
|
* move logic of prev, next, fast forward and rewind to ControlDispatcher
|
||||||
[#6926](https://github.com/google/ExoPlayer/issues/6926)).
|
[#6926](https://github.com/google/ExoPlayer/issues/6926)).
|
||||||
|
* Demo apps: Add
|
||||||
|
[GL demo app](https://github.com/google/ExoPlayer/tree/dev-v2/demos/gl) to
|
||||||
|
show how to render video to a `GLSurfaceView` while applying a GL shader.
|
||||||
|
([#6920](https://github.com/google/ExoPlayer/issues/6920)).
|
||||||
|
|
||||||
### 2.11.2 (TBD) ###
|
### 2.11.2 (TBD) ###
|
||||||
|
|
||||||
|
11
demos/gl/README.md
Normal file
11
demos/gl/README.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# ExoPlayer GL demo
|
||||||
|
|
||||||
|
This app demonstrates how to render video to a [GLSurfaceView][] while applying
|
||||||
|
a GL shader.
|
||||||
|
|
||||||
|
The shader shows an overlap bitmap on top of the video. The overlay bitmap is
|
||||||
|
drawn using an Android canvas, and includes the current frame's presentation
|
||||||
|
timestamp, to show how to get the timestamp of the frame currently in the
|
||||||
|
off-screen surface texture.
|
||||||
|
|
||||||
|
[GLSurfaceView]: https://developer.android.com/reference/android/opengl/GLSurfaceView
|
53
demos/gl/build.gradle
Normal file
53
demos/gl/build.gradle
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Copyright (C) 2020 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.
|
||||||
|
apply from: '../../constants.gradle'
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
versionName project.ext.releaseVersion
|
||||||
|
versionCode project.ext.releaseVersionCode
|
||||||
|
minSdkVersion project.ext.minSdkVersion
|
||||||
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
shrinkResources true
|
||||||
|
minifyEnabled true
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
// This demo app does not have translations.
|
||||||
|
disable 'MissingTranslation'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation project(modulePrefix + 'library-core')
|
||||||
|
implementation project(modulePrefix + 'library-ui')
|
||||||
|
implementation project(modulePrefix + 'library-dash')
|
||||||
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
|
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||||
|
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkVersion
|
||||||
|
}
|
49
demos/gl/src/main/AndroidManifest.xml
Normal file
49
demos/gl/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2020 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.
|
||||||
|
-->
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.google.android.exoplayer2.gldemo">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<uses-sdk/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/application_name">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="com.google.android.exoplayer.gldemo.action.VIEW"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:scheme="http"/>
|
||||||
|
<data android:scheme="https"/>
|
||||||
|
<data android:scheme="content"/>
|
||||||
|
<data android:scheme="asset"/>
|
||||||
|
<data android:scheme="file"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2020 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.
|
||||||
|
|
||||||
|
#extension GL_OES_EGL_image_external : require
|
||||||
|
precision mediump float;
|
||||||
|
// External texture containing video decoder output.
|
||||||
|
uniform samplerExternalOES tex_sampler_0;
|
||||||
|
// Texture containing the overlap bitmap.
|
||||||
|
uniform sampler2D tex_sampler_1;
|
||||||
|
// Horizontal scaling factor for the overlap bitmap.
|
||||||
|
uniform float scaleX;
|
||||||
|
// Vertical scaling factory for the overlap bitmap.
|
||||||
|
uniform float scaleY;
|
||||||
|
varying vec2 v_texcoord;
|
||||||
|
void main() {
|
||||||
|
vec4 videoColor = texture2D(tex_sampler_0, v_texcoord);
|
||||||
|
vec4 overlayColor = texture2D(tex_sampler_1,
|
||||||
|
vec2(v_texcoord.x * scaleX,
|
||||||
|
v_texcoord.y * scaleY));
|
||||||
|
// Blend the video decoder output and the overlay bitmap.
|
||||||
|
gl_FragColor = videoColor * (1.0 - overlayColor.a)
|
||||||
|
+ overlayColor * overlayColor.a;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2020 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.
|
||||||
|
attribute vec4 a_position;
|
||||||
|
attribute vec3 a_texcoord;
|
||||||
|
varying vec2 v_texcoord;
|
||||||
|
void main() {
|
||||||
|
gl_Position = a_position;
|
||||||
|
v_texcoord = a_texcoord.xy;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.gldemo;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import android.opengl.GLUtils;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.GlUtil;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Locale;
|
||||||
|
import javax.microedition.khronos.opengles.GL10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Video processor that demonstrates how to overlay a bitmap on video output using a GL shader. The
|
||||||
|
* bitmap is drawn using an Android {@link Canvas}.
|
||||||
|
*/
|
||||||
|
/* package */ final class BitmapOverlayVideoProcessor
|
||||||
|
implements VideoProcessingGLSurfaceView.VideoProcessor {
|
||||||
|
|
||||||
|
private static final int OVERLAY_WIDTH = 512;
|
||||||
|
private static final int OVERLAY_HEIGHT = 256;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final Paint paint;
|
||||||
|
private final int[] textures;
|
||||||
|
private final Bitmap overlayBitmap;
|
||||||
|
private final Bitmap logoBitmap;
|
||||||
|
private final Canvas overlayCanvas;
|
||||||
|
|
||||||
|
private int program;
|
||||||
|
@Nullable private GlUtil.Attribute[] attributes;
|
||||||
|
@Nullable private GlUtil.Uniform[] uniforms;
|
||||||
|
|
||||||
|
private float bitmapScaleX;
|
||||||
|
private float bitmapScaleY;
|
||||||
|
|
||||||
|
public BitmapOverlayVideoProcessor(Context context) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
paint = new Paint();
|
||||||
|
paint.setTextSize(64);
|
||||||
|
paint.setAntiAlias(true);
|
||||||
|
paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
||||||
|
textures = new int[1];
|
||||||
|
overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888);
|
||||||
|
overlayCanvas = new Canvas(overlayBitmap);
|
||||||
|
try {
|
||||||
|
logoBitmap =
|
||||||
|
((BitmapDrawable)
|
||||||
|
context.getPackageManager().getApplicationIcon(context.getPackageName()))
|
||||||
|
.getBitmap();
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
String vertexShaderCode =
|
||||||
|
loadAssetAsString(context, "bitmap_overlay_video_processor_vertex.glsl");
|
||||||
|
String fragmentShaderCode =
|
||||||
|
loadAssetAsString(context, "bitmap_overlay_video_processor_fragment.glsl");
|
||||||
|
program = GlUtil.compileProgram(vertexShaderCode, fragmentShaderCode);
|
||||||
|
GlUtil.Attribute[] attributes = GlUtil.getAttributes(program);
|
||||||
|
GlUtil.Uniform[] uniforms = GlUtil.getUniforms(program);
|
||||||
|
for (GlUtil.Attribute attribute : attributes) {
|
||||||
|
if (attribute.name.equals("a_position")) {
|
||||||
|
attribute.setBuffer(
|
||||||
|
new float[] {
|
||||||
|
-1.0f, -1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
|
||||||
|
1.0f, 0.0f, 1.0f,
|
||||||
|
},
|
||||||
|
4);
|
||||||
|
} else if (attribute.name.equals("a_texcoord")) {
|
||||||
|
attribute.setBuffer(
|
||||||
|
new float[] {
|
||||||
|
0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
|
||||||
|
},
|
||||||
|
3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.attributes = attributes;
|
||||||
|
this.uniforms = uniforms;
|
||||||
|
GLES20.glGenTextures(1, textures, 0);
|
||||||
|
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
|
||||||
|
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
|
||||||
|
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
|
||||||
|
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
|
||||||
|
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
|
||||||
|
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSurfaceSize(int width, int height) {
|
||||||
|
bitmapScaleX = (float) width / OVERLAY_WIDTH;
|
||||||
|
bitmapScaleY = (float) height / OVERLAY_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(int frameTexture, long frameTimestampUs) {
|
||||||
|
// Draw to the canvas and store it in a texture.
|
||||||
|
String text = String.format(Locale.US, "%.02f", frameTimestampUs / (float) C.MICROS_PER_SECOND);
|
||||||
|
overlayBitmap.eraseColor(Color.TRANSPARENT);
|
||||||
|
overlayCanvas.drawBitmap(logoBitmap, /* left= */ 32, /* top= */ 32, paint);
|
||||||
|
overlayCanvas.drawText(text, /* x= */ 200, /* y= */ 130, paint);
|
||||||
|
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
|
||||||
|
GLUtils.texSubImage2D(
|
||||||
|
GL10.GL_TEXTURE_2D, /* level= */ 0, /* xoffset= */ 0, /* yoffset= */ 0, overlayBitmap);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
|
||||||
|
// Run the shader program.
|
||||||
|
GlUtil.Uniform[] uniforms = Assertions.checkNotNull(this.uniforms);
|
||||||
|
GlUtil.Attribute[] attributes = Assertions.checkNotNull(this.attributes);
|
||||||
|
GLES20.glUseProgram(program);
|
||||||
|
for (GlUtil.Uniform uniform : uniforms) {
|
||||||
|
switch (uniform.name) {
|
||||||
|
case "tex_sampler_0":
|
||||||
|
uniform.setSamplerTexId(frameTexture, /* unit= */ 0);
|
||||||
|
break;
|
||||||
|
case "tex_sampler_1":
|
||||||
|
uniform.setSamplerTexId(textures[0], /* unit= */ 1);
|
||||||
|
break;
|
||||||
|
case "scaleX":
|
||||||
|
uniform.setFloat(bitmapScaleX);
|
||||||
|
break;
|
||||||
|
case "scaleY":
|
||||||
|
uniform.setFloat(bitmapScaleY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (GlUtil.Attribute copyExternalAttribute : attributes) {
|
||||||
|
copyExternalAttribute.bind();
|
||||||
|
}
|
||||||
|
for (GlUtil.Uniform copyExternalUniform : uniforms) {
|
||||||
|
copyExternalUniform.bind();
|
||||||
|
}
|
||||||
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||||
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String loadAssetAsString(Context context, String assetFileName) {
|
||||||
|
@Nullable InputStream inputStream = null;
|
||||||
|
try {
|
||||||
|
inputStream = context.getAssets().open(assetFileName);
|
||||||
|
return Util.fromUtf8Bytes(Util.toByteArray(inputStream));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
} finally {
|
||||||
|
Util.closeQuietly(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.gldemo;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||||
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.dash.DashMediaSource;
|
||||||
|
import com.google.android.exoplayer2.ui.PlayerView;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.EventLogger;
|
||||||
|
import com.google.android.exoplayer2.util.GlUtil;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity that demonstrates playback of video to an {@link android.opengl.GLSurfaceView} with
|
||||||
|
* postprocessing of the video content using GL.
|
||||||
|
*/
|
||||||
|
public final class MainActivity extends Activity {
|
||||||
|
|
||||||
|
private static final String TAG = "MainActivity";
|
||||||
|
|
||||||
|
private static final String DEFAULT_MEDIA_URI =
|
||||||
|
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv";
|
||||||
|
|
||||||
|
private static final String ACTION_VIEW = "com.google.android.exoplayer.gldemo.action.VIEW";
|
||||||
|
private static final String EXTENSION_EXTRA = "extension";
|
||||||
|
private static final String DRM_SCHEME_EXTRA = "drm_scheme";
|
||||||
|
private static final String DRM_LICENSE_URL_EXTRA = "drm_license_url";
|
||||||
|
|
||||||
|
@Nullable private PlayerView playerView;
|
||||||
|
@Nullable private VideoProcessingGLSurfaceView videoProcessingGLSurfaceView;
|
||||||
|
|
||||||
|
@Nullable private SimpleExoPlayer player;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.main_activity);
|
||||||
|
playerView = findViewById(R.id.player_view);
|
||||||
|
|
||||||
|
Context context = getApplicationContext();
|
||||||
|
boolean requestSecureSurface = getIntent().hasExtra(DRM_SCHEME_EXTRA);
|
||||||
|
if (requestSecureSurface && !GlUtil.isProtectedContentExtensionSupported(context)) {
|
||||||
|
Toast.makeText(
|
||||||
|
context, R.string.error_protected_content_extension_not_supported, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
|
||||||
|
new VideoProcessingGLSurfaceView(
|
||||||
|
context, requestSecureSurface, new BitmapOverlayVideoProcessor(context));
|
||||||
|
FrameLayout contentFrame = findViewById(R.id.exo_content_frame);
|
||||||
|
contentFrame.addView(videoProcessingGLSurfaceView);
|
||||||
|
this.videoProcessingGLSurfaceView = videoProcessingGLSurfaceView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
if (Util.SDK_INT > 23) {
|
||||||
|
initializePlayer();
|
||||||
|
if (playerView != null) {
|
||||||
|
playerView.onResume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (Util.SDK_INT <= 23 || player == null) {
|
||||||
|
initializePlayer();
|
||||||
|
if (playerView != null) {
|
||||||
|
playerView.onResume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (Util.SDK_INT <= 23) {
|
||||||
|
if (playerView != null) {
|
||||||
|
playerView.onPause();
|
||||||
|
}
|
||||||
|
releasePlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (Util.SDK_INT > 23) {
|
||||||
|
if (playerView != null) {
|
||||||
|
playerView.onPause();
|
||||||
|
}
|
||||||
|
releasePlayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializePlayer() {
|
||||||
|
Intent intent = getIntent();
|
||||||
|
String action = intent.getAction();
|
||||||
|
Uri uri =
|
||||||
|
ACTION_VIEW.equals(action)
|
||||||
|
? Assertions.checkNotNull(intent.getData())
|
||||||
|
: Uri.parse(DEFAULT_MEDIA_URI);
|
||||||
|
String userAgent = Util.getUserAgent(this, getString(R.string.application_name));
|
||||||
|
DrmSessionManager<ExoMediaCrypto> drmSessionManager;
|
||||||
|
if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) {
|
||||||
|
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
|
||||||
|
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
||||||
|
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
||||||
|
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
|
||||||
|
HttpMediaDrmCallback drmCallback =
|
||||||
|
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
||||||
|
drmSessionManager =
|
||||||
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
|
||||||
|
.build(drmCallback);
|
||||||
|
} else {
|
||||||
|
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
DataSource.Factory dataSourceFactory =
|
||||||
|
new DefaultDataSourceFactory(
|
||||||
|
this, Util.getUserAgent(this, getString(R.string.application_name)));
|
||||||
|
MediaSource mediaSource;
|
||||||
|
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
|
||||||
|
if (type == C.TYPE_DASH) {
|
||||||
|
mediaSource =
|
||||||
|
new DashMediaSource.Factory(dataSourceFactory)
|
||||||
|
.setDrmSessionManager(drmSessionManager)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
} else if (type == C.TYPE_OTHER) {
|
||||||
|
mediaSource =
|
||||||
|
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
|
.setDrmSessionManager(drmSessionManager)
|
||||||
|
.createMediaSource(uri);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
SimpleExoPlayer player = new SimpleExoPlayer.Builder(getApplicationContext()).build();
|
||||||
|
player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||||
|
player.setMediaSource(mediaSource);
|
||||||
|
player.prepare();
|
||||||
|
player.play();
|
||||||
|
VideoProcessingGLSurfaceView videoProcessingGLSurfaceView =
|
||||||
|
Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
|
||||||
|
videoProcessingGLSurfaceView.setVideoComponent(
|
||||||
|
Assertions.checkNotNull(player.getVideoComponent()));
|
||||||
|
Assertions.checkNotNull(playerView).setPlayer(player);
|
||||||
|
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
|
||||||
|
this.player = player;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releasePlayer() {
|
||||||
|
Assertions.checkNotNull(playerView).setPlayer(null);
|
||||||
|
if (player != null) {
|
||||||
|
player.release();
|
||||||
|
Assertions.checkNotNull(videoProcessingGLSurfaceView).setVideoComponent(null);
|
||||||
|
player = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.gldemo;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
import android.opengl.EGL14;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import android.opengl.GLSurfaceView;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.Surface;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.GlUtil;
|
||||||
|
import com.google.android.exoplayer2.util.TimedValueQueue;
|
||||||
|
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import javax.microedition.khronos.egl.EGL10;
|
||||||
|
import javax.microedition.khronos.egl.EGLConfig;
|
||||||
|
import javax.microedition.khronos.egl.EGLContext;
|
||||||
|
import javax.microedition.khronos.egl.EGLDisplay;
|
||||||
|
import javax.microedition.khronos.egl.EGLSurface;
|
||||||
|
import javax.microedition.khronos.opengles.GL10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link GLSurfaceView} that creates a GL context (optionally for protected content) and passes
|
||||||
|
* video frames to a {@link VideoProcessor} for drawing to the view.
|
||||||
|
*
|
||||||
|
* <p>This view must be created programmatically, as it is necessary to specify whether a context
|
||||||
|
* supporting protected content should be created at construction time.
|
||||||
|
*/
|
||||||
|
public final class VideoProcessingGLSurfaceView extends GLSurfaceView {
|
||||||
|
|
||||||
|
/** Processes video frames, provided via a GL texture. */
|
||||||
|
public interface VideoProcessor {
|
||||||
|
/** Performs any required GL initialization. */
|
||||||
|
void initialize();
|
||||||
|
|
||||||
|
/** Sets the size of the output surface in pixels. */
|
||||||
|
void setSurfaceSize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws using GL operations.
|
||||||
|
*
|
||||||
|
* @param frameTexture The ID of a GL texture containing a video frame.
|
||||||
|
* @param frameTimestampUs The presentation timestamp of the frame, in microseconds.
|
||||||
|
*/
|
||||||
|
void draw(int frameTexture, long frameTimestampUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;
|
||||||
|
|
||||||
|
private final VideoRenderer renderer;
|
||||||
|
private final Handler mainHandler;
|
||||||
|
|
||||||
|
@Nullable private SurfaceTexture surfaceTexture;
|
||||||
|
@Nullable private Surface surface;
|
||||||
|
@Nullable private Player.VideoComponent videoComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance. Pass {@code true} for {@code requireSecureContext} if the {@link
|
||||||
|
* GLSurfaceView GLSurfaceView's} associated GL context should handle secure content (if the
|
||||||
|
* device supports it).
|
||||||
|
*
|
||||||
|
* @param context The {@link Context}.
|
||||||
|
* @param requireSecureContext Whether a GL context supporting protected content should be
|
||||||
|
* created, if supported by the device.
|
||||||
|
* @param videoProcessor Processor that draws to the view.
|
||||||
|
*/
|
||||||
|
public VideoProcessingGLSurfaceView(
|
||||||
|
Context context, boolean requireSecureContext, VideoProcessor videoProcessor) {
|
||||||
|
super(context);
|
||||||
|
renderer = new VideoRenderer(videoProcessor);
|
||||||
|
mainHandler = new Handler();
|
||||||
|
setEGLContextClientVersion(2);
|
||||||
|
setEGLConfigChooser(
|
||||||
|
/* redSize= */ 8,
|
||||||
|
/* greenSize= */ 8,
|
||||||
|
/* blueSize= */ 8,
|
||||||
|
/* alphaSize= */ 8,
|
||||||
|
/* depthSize= */ 0,
|
||||||
|
/* stencilSize= */ 0);
|
||||||
|
setEGLContextFactory(
|
||||||
|
new EGLContextFactory() {
|
||||||
|
@Override
|
||||||
|
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
|
||||||
|
int[] glAttributes;
|
||||||
|
if (requireSecureContext) {
|
||||||
|
glAttributes =
|
||||||
|
new int[] {
|
||||||
|
EGL14.EGL_CONTEXT_CLIENT_VERSION,
|
||||||
|
2,
|
||||||
|
EGL_PROTECTED_CONTENT_EXT,
|
||||||
|
EGL14.EGL_TRUE,
|
||||||
|
EGL14.EGL_NONE
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
|
||||||
|
}
|
||||||
|
return egl.eglCreateContext(
|
||||||
|
display, eglConfig, /* share_context= */ EGL10.EGL_NO_CONTEXT, glAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
|
||||||
|
egl.eglDestroyContext(display, context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setEGLWindowSurfaceFactory(
|
||||||
|
new EGLWindowSurfaceFactory() {
|
||||||
|
@Override
|
||||||
|
public EGLSurface createWindowSurface(
|
||||||
|
EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) {
|
||||||
|
int[] attribsList =
|
||||||
|
requireSecureContext
|
||||||
|
? new int[] {EGL_PROTECTED_CONTENT_EXT, EGL14.EGL_TRUE, EGL10.EGL_NONE}
|
||||||
|
: new int[] {EGL10.EGL_NONE};
|
||||||
|
return egl.eglCreateWindowSurface(display, config, nativeWindow, attribsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) {
|
||||||
|
egl.eglDestroySurface(display, surface);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setRenderer(renderer);
|
||||||
|
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches or detaches (if {@code newVideoComponent} is {@code null}) this view from the video
|
||||||
|
* component of the player.
|
||||||
|
*
|
||||||
|
* @param newVideoComponent The new video component, or {@code null} to detach this view.
|
||||||
|
*/
|
||||||
|
public void setVideoComponent(@Nullable Player.VideoComponent newVideoComponent) {
|
||||||
|
if (newVideoComponent == videoComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (videoComponent != null) {
|
||||||
|
if (surface != null) {
|
||||||
|
videoComponent.clearVideoSurface(surface);
|
||||||
|
}
|
||||||
|
videoComponent.clearVideoFrameMetadataListener(renderer);
|
||||||
|
}
|
||||||
|
videoComponent = newVideoComponent;
|
||||||
|
if (videoComponent != null) {
|
||||||
|
videoComponent.setVideoFrameMetadataListener(renderer);
|
||||||
|
videoComponent.setVideoSurface(surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetachedFromWindow() {
|
||||||
|
super.onDetachedFromWindow();
|
||||||
|
// Post to make sure we occur in order with any onSurfaceTextureAvailable calls.
|
||||||
|
mainHandler.post(
|
||||||
|
() -> {
|
||||||
|
if (surface != null) {
|
||||||
|
if (videoComponent != null) {
|
||||||
|
videoComponent.setVideoSurface(null);
|
||||||
|
}
|
||||||
|
releaseSurface(surfaceTexture, surface);
|
||||||
|
surfaceTexture = null;
|
||||||
|
surface = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) {
|
||||||
|
mainHandler.post(
|
||||||
|
() -> {
|
||||||
|
SurfaceTexture oldSurfaceTexture = this.surfaceTexture;
|
||||||
|
Surface oldSurface = VideoProcessingGLSurfaceView.this.surface;
|
||||||
|
this.surfaceTexture = surfaceTexture;
|
||||||
|
this.surface = new Surface(surfaceTexture);
|
||||||
|
releaseSurface(oldSurfaceTexture, oldSurface);
|
||||||
|
if (videoComponent != null) {
|
||||||
|
videoComponent.setVideoSurface(surface);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void releaseSurface(
|
||||||
|
@Nullable SurfaceTexture oldSurfaceTexture, @Nullable Surface oldSurface) {
|
||||||
|
if (oldSurfaceTexture != null) {
|
||||||
|
oldSurfaceTexture.release();
|
||||||
|
}
|
||||||
|
if (oldSurface != null) {
|
||||||
|
oldSurface.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class VideoRenderer implements GLSurfaceView.Renderer, VideoFrameMetadataListener {
|
||||||
|
|
||||||
|
private final VideoProcessor videoProcessor;
|
||||||
|
private final AtomicBoolean frameAvailable;
|
||||||
|
private final TimedValueQueue<Long> sampleTimestampQueue;
|
||||||
|
|
||||||
|
private int texture;
|
||||||
|
@Nullable private SurfaceTexture surfaceTexture;
|
||||||
|
|
||||||
|
private boolean initialized;
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
private long frameTimestampUs;
|
||||||
|
|
||||||
|
public VideoRenderer(VideoProcessor videoProcessor) {
|
||||||
|
this.videoProcessor = videoProcessor;
|
||||||
|
frameAvailable = new AtomicBoolean();
|
||||||
|
sampleTimestampQueue = new TimedValueQueue<>();
|
||||||
|
width = -1;
|
||||||
|
height = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void onSurfaceCreated(GL10 gl, EGLConfig config) {
|
||||||
|
texture = GlUtil.createExternalTexture();
|
||||||
|
surfaceTexture = new SurfaceTexture(texture);
|
||||||
|
surfaceTexture.setOnFrameAvailableListener(
|
||||||
|
surfaceTexture -> {
|
||||||
|
frameAvailable.set(true);
|
||||||
|
requestRender();
|
||||||
|
});
|
||||||
|
onSurfaceTextureAvailable(surfaceTexture);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSurfaceChanged(GL10 gl, int width, int height) {
|
||||||
|
GLES20.glViewport(0, 0, width, height);
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDrawFrame(GL10 gl) {
|
||||||
|
if (videoProcessor == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!initialized) {
|
||||||
|
videoProcessor.initialize();
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width != -1 && height != -1) {
|
||||||
|
videoProcessor.setSurfaceSize(width, height);
|
||||||
|
width = -1;
|
||||||
|
height = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameAvailable.compareAndSet(true, false)) {
|
||||||
|
SurfaceTexture surfaceTexture = Assertions.checkNotNull(this.surfaceTexture);
|
||||||
|
surfaceTexture.updateTexImage();
|
||||||
|
long lastFrameTimestampNs = surfaceTexture.getTimestamp();
|
||||||
|
Long frameTimestampUs = sampleTimestampQueue.poll(lastFrameTimestampNs);
|
||||||
|
if (frameTimestampUs != null) {
|
||||||
|
this.frameTimestampUs = frameTimestampUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
videoProcessor.draw(texture, frameTimestampUs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoFrameAboutToBeRendered(
|
||||||
|
long presentationTimeUs,
|
||||||
|
long releaseTimeNs,
|
||||||
|
Format format,
|
||||||
|
@Nullable MediaFormat mediaFormat) {
|
||||||
|
sampleTimestampQueue.add(releaseTimeNs, presentationTimeUs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
demos/gl/src/main/res/layout/main_activity.xml
Normal file
30
demos/gl/src/main/res/layout/main_activity.xml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2020 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.
|
||||||
|
-->
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:keepScreenOn="true">
|
||||||
|
|
||||||
|
<com.google.android.exoplayer2.ui.PlayerView
|
||||||
|
android:id="@+id/player_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:surface_type="none"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
BIN
demos/gl/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
demos/gl/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
BIN
demos/gl/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.8 KiB |
BIN
demos/gl/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.3 KiB |
BIN
demos/gl/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
demos/gl/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
22
demos/gl/src/main/res/values/strings.xml
Normal file
22
demos/gl/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2020 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.
|
||||||
|
-->
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="application_name">ExoPlayer GL demo</string>
|
||||||
|
|
||||||
|
<string name="error_protected_content_extension_not_supported">The GL protected content extension is not supported.</string>
|
||||||
|
|
||||||
|
</resources>
|
@ -17,23 +17,235 @@ package com.google.android.exoplayer2.util;
|
|||||||
|
|
||||||
import static android.opengl.GLU.gluErrorString;
|
import static android.opengl.GLU.gluErrorString;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.opengl.EGL14;
|
||||||
|
import android.opengl.EGLDisplay;
|
||||||
import android.opengl.GLES11Ext;
|
import android.opengl.GLES11Ext;
|
||||||
import android.opengl.GLES20;
|
import android.opengl.GLES20;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
|
import java.nio.Buffer;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
|
import javax.microedition.khronos.egl.EGL10;
|
||||||
|
|
||||||
/** GL utility methods. */
|
/** GL utilities. */
|
||||||
public final class GlUtil {
|
public final class GlUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}.
|
||||||
|
*/
|
||||||
|
public static final class Attribute {
|
||||||
|
|
||||||
|
/** The name of the attribute in the GLSL sources. */
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
private final int index;
|
||||||
|
private final int location;
|
||||||
|
|
||||||
|
@Nullable private Buffer buffer;
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new GL attribute.
|
||||||
|
*
|
||||||
|
* @param program The identifier of a compiled and linked GLSL shader program.
|
||||||
|
* @param index The index of the attribute. After this instance has been constructed, the name
|
||||||
|
* of the attribute is available via the {@link #name} field.
|
||||||
|
*/
|
||||||
|
public Attribute(int program, int index) {
|
||||||
|
int[] len = new int[1];
|
||||||
|
GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, len, 0);
|
||||||
|
|
||||||
|
int[] type = new int[1];
|
||||||
|
int[] size = new int[1];
|
||||||
|
byte[] nameBytes = new byte[len[0]];
|
||||||
|
int[] ignore = new int[1];
|
||||||
|
|
||||||
|
GLES20.glGetActiveAttrib(program, index, len[0], ignore, 0, size, 0, type, 0, nameBytes, 0);
|
||||||
|
name = new String(nameBytes, 0, strlen(nameBytes));
|
||||||
|
location = GLES20.glGetAttribLocation(program, name);
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size}
|
||||||
|
* elements) to this {@link Attribute}.
|
||||||
|
*
|
||||||
|
* @param buffer Buffer to bind to this attribute.
|
||||||
|
* @param size Number of elements per vertex.
|
||||||
|
*/
|
||||||
|
public void setBuffer(float[] buffer, int size) {
|
||||||
|
this.buffer = createBuffer(buffer);
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}.
|
||||||
|
*
|
||||||
|
* <p>Should be called before each drawing call.
|
||||||
|
*/
|
||||||
|
public void bind() {
|
||||||
|
Buffer buffer = Assertions.checkNotNull(this.buffer, "call setBuffer before bind");
|
||||||
|
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
|
||||||
|
GLES20.glVertexAttribPointer(
|
||||||
|
location,
|
||||||
|
size, // count
|
||||||
|
GLES20.GL_FLOAT, // type
|
||||||
|
false, // normalize
|
||||||
|
0, // stride
|
||||||
|
buffer);
|
||||||
|
GLES20.glEnableVertexAttribArray(index);
|
||||||
|
checkGlError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}.
|
||||||
|
*/
|
||||||
|
public static final class Uniform {
|
||||||
|
|
||||||
|
/** The name of the uniform in the GLSL sources. */
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
private final int location;
|
||||||
|
private final int type;
|
||||||
|
private final float[] value;
|
||||||
|
|
||||||
|
private int texId;
|
||||||
|
private int unit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new GL uniform.
|
||||||
|
*
|
||||||
|
* @param program The identifier of a compiled and linked GLSL shader program.
|
||||||
|
* @param index The index of the uniform. After this instance has been constructed, the name of
|
||||||
|
* the uniform is available via the {@link #name} field.
|
||||||
|
*/
|
||||||
|
public Uniform(int program, int index) {
|
||||||
|
int[] len = new int[1];
|
||||||
|
GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, len, 0);
|
||||||
|
|
||||||
|
int[] type = new int[1];
|
||||||
|
int[] size = new int[1];
|
||||||
|
byte[] name = new byte[len[0]];
|
||||||
|
int[] ignore = new int[1];
|
||||||
|
|
||||||
|
GLES20.glGetActiveUniform(program, index, len[0], ignore, 0, size, 0, type, 0, name, 0);
|
||||||
|
this.name = new String(name, 0, strlen(name));
|
||||||
|
location = GLES20.glGetUniformLocation(program, this.name);
|
||||||
|
this.type = type[0];
|
||||||
|
|
||||||
|
value = new float[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
|
||||||
|
*
|
||||||
|
* @param texId The GL texture identifier from which to sample.
|
||||||
|
* @param unit The GL texture unit index.
|
||||||
|
*/
|
||||||
|
public void setSamplerTexId(int texId, int unit) {
|
||||||
|
this.texId = texId;
|
||||||
|
this.unit = unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
|
||||||
|
public void setFloat(float value) {
|
||||||
|
this.value[0] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)} or
|
||||||
|
* {@link #setFloat(float)}.
|
||||||
|
*
|
||||||
|
* <p>Should be called before each drawing call.
|
||||||
|
*/
|
||||||
|
public void bind() {
|
||||||
|
if (type == GLES20.GL_FLOAT) {
|
||||||
|
GLES20.glUniform1fv(location, 1, value, 0);
|
||||||
|
checkGlError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texId == 0) {
|
||||||
|
throw new IllegalStateException("call setSamplerTexId before bind");
|
||||||
|
}
|
||||||
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
|
||||||
|
if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES) {
|
||||||
|
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
|
||||||
|
} else if (type == GLES20.GL_SAMPLER_2D) {
|
||||||
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("unexpected uniform type: " + type);
|
||||||
|
}
|
||||||
|
GLES20.glUniform1i(location, unit);
|
||||||
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||||
|
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||||
|
GLES20.glTexParameteri(
|
||||||
|
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||||
|
GLES20.glTexParameteri(
|
||||||
|
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||||
|
checkGlError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final String TAG = "GlUtil";
|
private static final String TAG = "GlUtil";
|
||||||
|
|
||||||
|
private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content";
|
||||||
|
private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
|
||||||
|
|
||||||
/** Class only contains static methods. */
|
/** Class only contains static methods. */
|
||||||
private GlUtil() {}
|
private GlUtil() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether creating a GL context with {@value EXTENSION_PROTECTED_CONTENT} is possible. If
|
||||||
|
* {@code true}, the device supports a protected output path for DRM content when using GL.
|
||||||
|
*/
|
||||||
|
@TargetApi(24)
|
||||||
|
public static boolean isProtectedContentExtensionSupported(Context context) {
|
||||||
|
if (Util.SDK_INT < 24) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Util.SDK_INT < 26 && ("samsung".equals(Util.MANUFACTURER) || "XT1650".equals(Util.MODEL))) {
|
||||||
|
// Samsung devices running Nougat are known to be broken. See
|
||||||
|
// https://github.com/google/ExoPlayer/issues/3373 and [Internal: b/37197802].
|
||||||
|
// Moto Z XT1650 is also affected. See
|
||||||
|
// https://github.com/google/ExoPlayer/issues/3215.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Util.SDK_INT < 26
|
||||||
|
&& !context
|
||||||
|
.getPackageManager()
|
||||||
|
.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
|
||||||
|
// Pre API level 26 devices were not well tested unless they supported VR mode.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||||
|
@Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
|
||||||
|
return eglExtensions != null && eglExtensions.contains(EXTENSION_PROTECTED_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether creating a GL context with {@value EXTENSION_SURFACELESS_CONTEXT} is possible.
|
||||||
|
*/
|
||||||
|
@TargetApi(17)
|
||||||
|
public static boolean isSurfacelessContextExtensionSupported() {
|
||||||
|
if (Util.SDK_INT < 17) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||||
|
@Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
|
||||||
|
return eglExtensions != null && eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is an OpenGl error, logs the error and if {@link
|
* If there is an OpenGl error, logs the error and if {@link
|
||||||
* ExoPlayerLibraryInfo#GL_ASSERTIONS_ENABLED} is true throws a {@link RuntimeException}.
|
* ExoPlayerLibraryInfo#GL_ASSERTIONS_ENABLED} is true throws a {@link RuntimeException}.
|
||||||
@ -90,6 +302,34 @@ public final class GlUtil {
|
|||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the {@link Attribute}s in the specified {@code program}. */
|
||||||
|
public static Attribute[] getAttributes(int program) {
|
||||||
|
int[] attributeCount = new int[1];
|
||||||
|
GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, 0);
|
||||||
|
if (attributeCount[0] != 2) {
|
||||||
|
throw new IllegalStateException("expected two attributes");
|
||||||
|
}
|
||||||
|
|
||||||
|
Attribute[] attributes = new Attribute[attributeCount[0]];
|
||||||
|
for (int i = 0; i < attributeCount[0]; i++) {
|
||||||
|
attributes[i] = new Attribute(program, i);
|
||||||
|
}
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the {@link Uniform}s in the specified {@code program}. */
|
||||||
|
public static Uniform[] getUniforms(int program) {
|
||||||
|
int[] uniformCount = new int[1];
|
||||||
|
GLES20.glGetProgramiv(program, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, 0);
|
||||||
|
|
||||||
|
Uniform[] uniforms = new Uniform[uniformCount[0]];
|
||||||
|
for (int i = 0; i < uniformCount[0]; i++) {
|
||||||
|
uniforms[i] = new Uniform(program, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniforms;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates a FloatBuffer with the given data.
|
* Allocates a FloatBuffer with the given data.
|
||||||
*
|
*
|
||||||
@ -151,4 +391,14 @@ public final class GlUtil {
|
|||||||
throw new RuntimeException(errorMsg);
|
throw new RuntimeException(errorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the length of the null-terminated string in {@code strVal}. */
|
||||||
|
private static int strlen(byte[] strVal) {
|
||||||
|
for (int i = 0; i < strVal.length; ++i) {
|
||||||
|
if (strVal[i] == '\0') {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strVal.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,7 @@ import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_P
|
|||||||
import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_SURFACELESS_CONTEXT;
|
import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_SURFACELESS_CONTEXT;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.SurfaceTexture;
|
import android.graphics.SurfaceTexture;
|
||||||
import android.opengl.EGL14;
|
|
||||||
import android.opengl.EGLDisplay;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Handler.Callback;
|
import android.os.Handler.Callback;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
@ -34,9 +31,9 @@ import androidx.annotation.RequiresApi;
|
|||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.EGLSurfaceTexture;
|
import com.google.android.exoplayer2.util.EGLSurfaceTexture;
|
||||||
import com.google.android.exoplayer2.util.EGLSurfaceTexture.SecureMode;
|
import com.google.android.exoplayer2.util.EGLSurfaceTexture.SecureMode;
|
||||||
|
import com.google.android.exoplayer2.util.GlUtil;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import javax.microedition.khronos.egl.EGL10;
|
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/** A dummy {@link Surface}. */
|
/** A dummy {@link Surface}. */
|
||||||
@ -45,9 +42,6 @@ public final class DummySurface extends Surface {
|
|||||||
|
|
||||||
private static final String TAG = "DummySurface";
|
private static final String TAG = "DummySurface";
|
||||||
|
|
||||||
private static final String EXTENSION_PROTECTED_CONTENT = "EGL_EXT_protected_content";
|
|
||||||
private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the surface is secure.
|
* Whether the surface is secure.
|
||||||
*/
|
*/
|
||||||
@ -67,7 +61,7 @@ public final class DummySurface extends Surface {
|
|||||||
*/
|
*/
|
||||||
public static synchronized boolean isSecureSupported(Context context) {
|
public static synchronized boolean isSecureSupported(Context context) {
|
||||||
if (!secureModeInitialized) {
|
if (!secureModeInitialized) {
|
||||||
secureMode = Util.SDK_INT < 24 ? SECURE_MODE_NONE : getSecureModeV24(context);
|
secureMode = getSecureMode(context);
|
||||||
secureModeInitialized = true;
|
secureModeInitialized = true;
|
||||||
}
|
}
|
||||||
return secureMode != SECURE_MODE_NONE;
|
return secureMode != SECURE_MODE_NONE;
|
||||||
@ -119,34 +113,21 @@ public final class DummySurface extends Surface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(24)
|
@SecureMode
|
||||||
private static @SecureMode int getSecureModeV24(Context context) {
|
private static int getSecureMode(Context context) {
|
||||||
if (Util.SDK_INT < 26 && ("samsung".equals(Util.MANUFACTURER) || "XT1650".equals(Util.MODEL))) {
|
if (GlUtil.isProtectedContentExtensionSupported(context)) {
|
||||||
// Samsung devices running Nougat are known to be broken. See
|
if (GlUtil.isSurfacelessContextExtensionSupported()) {
|
||||||
// https://github.com/google/ExoPlayer/issues/3373 and [Internal: b/37197802].
|
return SECURE_MODE_SURFACELESS_CONTEXT;
|
||||||
// Moto Z XT1650 is also affected. See
|
} else {
|
||||||
// https://github.com/google/ExoPlayer/issues/3215.
|
// If we can't use surfaceless contexts, we use a protected 1 * 1 pixel buffer surface.
|
||||||
|
// This may require support for EXT_protected_surface, but in practice it works on some
|
||||||
|
// devices that don't have that extension. See also
|
||||||
|
// https://github.com/google/ExoPlayer/issues/3558.
|
||||||
|
return SECURE_MODE_PROTECTED_PBUFFER;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return SECURE_MODE_NONE;
|
return SECURE_MODE_NONE;
|
||||||
}
|
}
|
||||||
if (Util.SDK_INT < 26 && !context.getPackageManager().hasSystemFeature(
|
|
||||||
PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE)) {
|
|
||||||
// Pre API level 26 devices were not well tested unless they supported VR mode.
|
|
||||||
return SECURE_MODE_NONE;
|
|
||||||
}
|
|
||||||
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
|
||||||
String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
|
|
||||||
if (eglExtensions == null) {
|
|
||||||
return SECURE_MODE_NONE;
|
|
||||||
}
|
|
||||||
if (!eglExtensions.contains(EXTENSION_PROTECTED_CONTENT)) {
|
|
||||||
return SECURE_MODE_NONE;
|
|
||||||
}
|
|
||||||
// If we can't use surfaceless contexts, we use a protected 1 * 1 pixel buffer surface. This may
|
|
||||||
// require support for EXT_protected_surface, but in practice it works on some devices that
|
|
||||||
// don't have that extension. See also https://github.com/google/ExoPlayer/issues/3558.
|
|
||||||
return eglExtensions.contains(EXTENSION_SURFACELESS_CONTEXT)
|
|
||||||
? SECURE_MODE_SURFACELESS_CONTEXT
|
|
||||||
: SECURE_MODE_PROTECTED_PBUFFER;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DummySurfaceThread extends HandlerThread implements Callback {
|
private static class DummySurfaceThread extends HandlerThread implements Callback {
|
||||||
|
@ -20,10 +20,12 @@ if (gradle.ext.has('exoplayerModulePrefix')) {
|
|||||||
|
|
||||||
include modulePrefix + 'demo'
|
include modulePrefix + 'demo'
|
||||||
include modulePrefix + 'demo-cast'
|
include modulePrefix + 'demo-cast'
|
||||||
|
include modulePrefix + 'demo-gl'
|
||||||
include modulePrefix + 'demo-surface'
|
include modulePrefix + 'demo-surface'
|
||||||
include modulePrefix + 'playbacktests'
|
include modulePrefix + 'playbacktests'
|
||||||
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
|
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
|
||||||
project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast')
|
project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast')
|
||||||
|
project(modulePrefix + 'demo-gl').projectDir = new File(rootDir, 'demos/gl')
|
||||||
project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
|
project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
|
||||||
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')
|
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user