├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle ├── libs │ ├── lebwebrtcsdk-2.0.8.aar │ └── lebwebrtcsdk-2.0.9.aar ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── tencent │ │ └── xbright │ │ └── lebwebrtcdemo │ │ ├── main │ │ ├── MainActivity.java │ │ └── PlaybackTabFragment.java │ │ ├── playback │ │ ├── DemoActivityParameter.java │ │ └── LEBWebRTCDemoActivity.java │ │ └── utils │ │ └── AsyncHttpURLConnection.java │ └── res │ ├── drawable-hdpi │ ├── cloud_logo.png │ ├── disconnect.png │ ├── ic_action_full_screen.png │ ├── ic_action_return_from_full_screen.png │ ├── ic_launcher.png │ └── ic_loopback_call.png │ ├── drawable-ldpi │ ├── disconnect.png │ ├── ic_action_full_screen.png │ ├── ic_action_return_from_full_screen.png │ ├── ic_launcher.png │ └── ic_loopback_call.png │ ├── drawable-mdpi │ ├── disconnect.png │ ├── ic_action_full_screen.png │ ├── ic_action_return_from_full_screen.png │ ├── ic_launcher.png │ └── ic_loopback_call.png │ ├── drawable-xhdpi │ ├── disconnect.png │ ├── ic_action_full_screen.png │ ├── ic_action_return_from_full_screen.png │ ├── ic_launcher.png │ └── ic_loopback_call.png │ ├── layout │ ├── activity_call.xml │ ├── activity_main.xml │ ├── activity_webrtc_demo.xml │ ├── activity_webrtc_push_demo.xml │ ├── fragment_playback_tab.xml │ ├── fragment_publish_tab.xml │ └── simple_playback_fragment.xml │ ├── menu │ ├── bottom_nav_menu.xml │ └── playback_setting.xml │ ├── navigation │ └── main_navigation.xml │ ├── values-v17 │ └── styles.xml │ └── values │ ├── arrays.xml │ ├── colors.xml │ ├── dimens.xml │ └── strings.xml ├── build.gradle ├── docs ├── Changelog.md ├── leb_android_sdk.md └── leb_signal_spec.pdf ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.DS_Store 3 | .idea 4 | .gradle 5 | build 6 | local.properties 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 腾讯云 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 快直播 LEB (超低延迟直播) Android SDK 2 | 3 | ## 快直播产品说明 4 | https://cloud.tencent.com/product/leb 5 | 6 | ## 接入文档 7 | https://github.com/tencentyun/leb-android-sdk/blob/master/docs/leb_android_sdk.md 8 | 9 | ## http信令文档 10 | https://github.com/tencentyun/leb-android-sdk/blob/master/docs/leb_signal_spec.pdf 11 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | defaultConfig { 6 | applicationId "com.tencent.xbright.lebwebrtcdemo" 7 | minSdkVersion rootProject.ext.minSdkVersion 8 | targetSdkVersion rootProject.ext.targetSdkVersion 9 | versionCode rootProject.ext.releaseVersionCode 10 | versionName rootProject.ext.releaseVersionName 11 | 12 | sourceSets.main { 13 | java.srcDirs = [ 14 | "src/main/java", 15 | ] 16 | jniLibs.srcDir 'libs' 17 | } 18 | 19 | ndk { 20 | abiFilters 'arm64-v8a', 'armeabi-v7a' 21 | } 22 | } 23 | buildTypes { 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_1_8 32 | targetCompatibility JavaVersion.VERSION_1_8 33 | } 34 | 35 | lintOptions { 36 | checkReleaseBuilds false 37 | // Or, if you prefer, you can continue to check for errors in release builds, 38 | // but continue the build even when errors are found: 39 | abortOnError false 40 | } 41 | } 42 | 43 | repositories { 44 | flatDir { 45 | dirs 'libs' //this way we can find the .aar file in libs folder 46 | } 47 | } 48 | 49 | dependencies { 50 | implementation fileTree(dir: 'libs', include: ['*.jar']) 51 | implementation 'androidx.appcompat:appcompat:1.0.2' 52 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 53 | implementation 'androidx.navigation:navigation-fragment:2.3.2' 54 | implementation 'androidx.navigation:navigation-ui:2.3.2' 55 | implementation 'com.google.android.material:material:1.0.0' 56 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 57 | testImplementation 'junit:junit:4.12' 58 | androidTestImplementation 'androidx.test:runner:1.2.0' 59 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 60 | debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6' 61 | 62 | // implementation 'com.tencent.xbright:lebwebrtcsdk:2.0.9' //deprecated 63 | implementation (name:'lebwebrtcsdk-2.0.9', ext:"aar") 64 | 65 | implementation 'com.tencent.bugly:crashreport:latest.release' //其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如2.1.9 66 | implementation 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新Bugly NDK版本号,也可以指定明确的版本号,例如3.0 67 | } 68 | 69 | -------------------------------------------------------------------------------- /app/libs/lebwebrtcsdk-2.0.8.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/libs/lebwebrtcsdk-2.0.8.aar -------------------------------------------------------------------------------- /app/libs/lebwebrtcsdk-2.0.9.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/libs/lebwebrtcsdk-2.0.9.aar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 48 | 49 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/tencent/xbright/lebwebrtcdemo/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tencent.xbright.lebwebrtcdemo.main; 2 | 3 | import android.os.Bundle; 4 | import android.view.MenuItem; 5 | import android.view.View; 6 | 7 | import com.google.android.material.bottomnavigation.BottomNavigationView; 8 | import com.tencent.bugly.crashreport.CrashReport; 9 | import com.tencent.xbright.lebwebrtcdemo.R; 10 | 11 | import androidx.appcompat.app.AppCompatActivity; 12 | import androidx.navigation.NavController; 13 | import androidx.navigation.Navigation; 14 | import androidx.navigation.ui.AppBarConfiguration; 15 | import androidx.navigation.ui.NavigationUI; 16 | 17 | import java.util.HashSet; 18 | import java.util.Set; 19 | 20 | public class MainActivity extends AppCompatActivity { 21 | private static final String TAG = "MainActivity"; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | setContentView(R.layout.activity_main); 27 | 28 | // bugly初始化 29 | CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(getApplicationContext()); 30 | strategy.setAppChannel("myChannel"); //设置渠道 31 | strategy.setAppVersion("2.0.1"); //App的版本, 这里设置了SDK version 32 | strategy.setAppPackageName("com.tencent.xbright.lebwebrtcdemo"); //App的包名 33 | CrashReport.initCrashReport(getApplicationContext(), "e3243444c9", false, strategy); 34 | 35 | setupNavigation(); 36 | } 37 | 38 | private void setupNavigation() { 39 | BottomNavigationView navigationView = findViewById(R.id.nav_view); 40 | View publishMenu = navigationView.findViewById(R.id.navigation_publish); 41 | 42 | boolean enablePublish = true; 43 | try { 44 | Class.forName("com.tencent.xbright.lebwebrtcdemo.main.PublishTabFragment"); 45 | publishMenu.setVisibility(View.VISIBLE); 46 | } catch (ClassNotFoundException ex) { 47 | publishMenu.setVisibility(View.GONE); 48 | enablePublish = false; 49 | } 50 | 51 | NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); 52 | Set navIds = new HashSet<>(); 53 | navIds.add(R.id.navigation_playback); 54 | if (enablePublish) { 55 | navIds.add(R.id.navigation_publish); 56 | }; 57 | AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navIds).build(); 58 | NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); 59 | NavigationUI.setupWithNavController(navigationView, navController); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/tencent/xbright/lebwebrtcdemo/main/PlaybackTabFragment.java: -------------------------------------------------------------------------------- 1 | package com.tencent.xbright.lebwebrtcdemo.main; 2 | 3 | import android.Manifest; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Button; 11 | import android.widget.EditText; 12 | import android.widget.RadioGroup; 13 | import android.widget.Switch; 14 | 15 | import androidx.annotation.NonNull; 16 | import androidx.annotation.Nullable; 17 | import androidx.fragment.app.Fragment; 18 | 19 | import com.tencent.xbright.lebwebrtcdemo.playback.DemoActivityParameter; 20 | import com.tencent.xbright.lebwebrtcdemo.playback.LEBWebRTCDemoActivity; 21 | import com.tencent.xbright.lebwebrtcdemo.R; 22 | import com.tencent.xbright.lebwebrtcsdk.LEBWebRTCParameters; 23 | 24 | import static android.widget.TextView.BufferType.EDITABLE; 25 | 26 | public class PlaybackTabFragment extends Fragment { 27 | private static final String TAG = "PlaybackTabFragment"; 28 | 29 | private EditText streamUrlText; 30 | private String streamUrl = "webrtc://5664.liveplay.myqcloud.com/live/5664_harchar1"; 31 | private EditText pullUrlText; 32 | private String pullUrl = "http://webrtc.liveplay.myqcloud.com/webrtc/v1/pullstream";//140.249.28.162 33 | private String stopUrl = "http://webrtc.liveplay.myqcloud.com/webrtc/v1/stopstream"; 34 | 35 | private int audioFormat = LEBWebRTCParameters.OPUS; //LEBWebRTCParameters.AAC_LATM, LEBWebRTCParameters.AAC_ADTS 36 | 37 | private boolean receiveAudio = true; 38 | private boolean receiveVideo = true; 39 | 40 | private EditText minJitterDelayText; 41 | private int minJitterDelayMs = 1000; 42 | 43 | @Override 44 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 45 | View view = inflater.inflate(R.layout.fragment_playback_tab, container, false); 46 | return view; 47 | } 48 | 49 | @Override 50 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 51 | Button btnConnect = view.findViewById(R.id.connect); 52 | btnConnect.setOnClickListener(this::startPlayback); 53 | 54 | streamUrlText = view.findViewById(R.id.stream_url); 55 | streamUrlText.setHint(streamUrl); 56 | pullUrlText = view.findViewById(R.id.signal_url); 57 | pullUrlText.setHint(pullUrl); 58 | 59 | minJitterDelayText = view.findViewById(R.id.min_delay); 60 | minJitterDelayText.setHint(String.valueOf(minJitterDelayMs)); 61 | 62 | RadioGroup playMode = view.findViewById(R.id.play_mode); 63 | playMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 64 | @Override 65 | public void onCheckedChanged(RadioGroup group, int checkedId) { 66 | switch (checkedId) { 67 | case R.id.play_normal: 68 | receiveAudio = true; 69 | receiveVideo = true; 70 | break; 71 | case R.id.play_audio_only: 72 | receiveAudio = true; 73 | receiveVideo = false; 74 | break; 75 | case R.id.play_video_only: 76 | receiveAudio = false; 77 | receiveVideo = true; 78 | break; 79 | } 80 | Log.d(TAG, "playMode receiveAudio: " + receiveAudio + " receiveVideo: " + receiveVideo); 81 | } 82 | }); 83 | 84 | RadioGroup audioCodec = view.findViewById(R.id.audio); 85 | audioCodec.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 86 | @Override 87 | public void onCheckedChanged(RadioGroup group, int checkedId) { 88 | switch (checkedId) { 89 | case R.id.opus: 90 | audioFormat = LEBWebRTCParameters.OPUS; 91 | break; 92 | case R.id.aac_latm: 93 | audioFormat = LEBWebRTCParameters.AAC_LATM; 94 | break; 95 | case R.id.aac_adts: 96 | audioFormat = LEBWebRTCParameters.AAC_ADTS; 97 | break; 98 | } 99 | Log.d(TAG, "audioFormat: " + audioFormat); 100 | } 101 | }); 102 | 103 | if (Build.VERSION.SDK_INT >= 23) { 104 | requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0); 105 | } 106 | } 107 | 108 | private void startPlayback(View v) { 109 | if (streamUrlText.getText().toString().startsWith("webrtc://")) { 110 | streamUrl = streamUrlText.getText().toString(); 111 | } 112 | 113 | if (pullUrlText.getText().toString().startsWith("https://") || pullUrlText.getText().toString().startsWith("http://")) { 114 | pullUrl = pullUrlText.getText().toString(); 115 | 116 | } 117 | if (!minJitterDelayText.getText().toString().isEmpty()) { 118 | minJitterDelayMs = Integer.parseInt(minJitterDelayText.getText().toString()); 119 | } 120 | 121 | String stream = streamUrl.replace("livepush", "liveplay"); 122 | View root = getView(); 123 | DemoActivityParameter parameter = new DemoActivityParameter(); 124 | parameter.mEncryption = ((Switch)root.findViewById(R.id.encrypted_switch)).isChecked(); 125 | parameter.mEnableHwDecode = ((Switch)root.findViewById(R.id.video_hwaccel_switch)).isChecked(); 126 | parameter.mReceiveAudio = receiveAudio; 127 | parameter.mReceiveVideo = receiveVideo; 128 | parameter.mSEICallback = ((Switch)root.findViewById(R.id.sei_callback)).isChecked(); 129 | parameter.mAudioFormat = audioFormat; 130 | parameter.mMinJitterDelayMs = minJitterDelayMs; 131 | parameter.mPlaybackStreamUrl = stream; 132 | parameter.mPlaybackPullUrl = pullUrl; 133 | parameter.mPlaybackStopUrl = stopUrl; 134 | LEBWebRTCDemoActivity.start(getActivity(), parameter); 135 | } 136 | 137 | @Override 138 | public void onDestroyView() { 139 | super.onDestroyView(); 140 | streamUrlText = null; 141 | pullUrlText = null; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /app/src/main/java/com/tencent/xbright/lebwebrtcdemo/playback/DemoActivityParameter.java: -------------------------------------------------------------------------------- 1 | package com.tencent.xbright.lebwebrtcdemo.playback; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.tencent.xbright.lebwebrtcsdk.LEBWebRTCParameters; 7 | 8 | public class DemoActivityParameter implements Parcelable { 9 | public boolean mEncryption = true; 10 | 11 | public boolean mEnableHwDecode = true; 12 | 13 | public boolean mReceiveAudio = true; 14 | 15 | public boolean mReceiveVideo = true; 16 | 17 | public boolean mSEICallback = false; 18 | 19 | public int mAudioFormat = LEBWebRTCParameters.OPUS; 20 | 21 | public int mMinJitterDelayMs = 1000; 22 | public String mPlaybackStreamUrl; 23 | public String mPlaybackPullUrl; 24 | public String mPlaybackStopUrl; 25 | 26 | public DemoActivityParameter() { 27 | } 28 | 29 | protected DemoActivityParameter(Parcel in) { 30 | mEncryption = in.readByte() != 0; 31 | mEnableHwDecode = in.readByte() != 0; 32 | mReceiveAudio = in.readByte() != 0; 33 | mReceiveVideo = in.readByte() != 0; 34 | mSEICallback = in.readByte() != 0; 35 | mAudioFormat = in.readInt(); 36 | mMinJitterDelayMs = in.readInt(); 37 | mPlaybackStreamUrl = in.readString(); 38 | mPlaybackPullUrl = in.readString(); 39 | mPlaybackStopUrl = in.readString(); 40 | } 41 | 42 | @Override 43 | public void writeToParcel(Parcel dest, int flags) { 44 | dest.writeByte((byte) (mEncryption ? 1 : 0)); 45 | dest.writeByte((byte) (mEnableHwDecode ? 1 : 0)); 46 | dest.writeByte((byte) (mReceiveAudio ? 1 : 0)); 47 | dest.writeByte((byte) (mReceiveVideo ? 1 : 0)); 48 | dest.writeByte((byte) (mSEICallback ? 1 : 0)); 49 | dest.writeInt(mAudioFormat); 50 | dest.writeInt(mMinJitterDelayMs); 51 | dest.writeString(mPlaybackStreamUrl); 52 | dest.writeString(mPlaybackPullUrl); 53 | dest.writeString(mPlaybackStopUrl); 54 | } 55 | 56 | @Override 57 | public int describeContents() { 58 | return 0; 59 | } 60 | 61 | public static final Creator CREATOR = new Creator() { 62 | @Override 63 | public DemoActivityParameter createFromParcel(Parcel in) { 64 | return new DemoActivityParameter(in); 65 | } 66 | 67 | @Override 68 | public DemoActivityParameter[] newArray(int size) { 69 | return new DemoActivityParameter[size]; 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/tencent/xbright/lebwebrtcdemo/playback/LEBWebRTCDemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.tencent.xbright.lebwebrtcdemo.playback; 2 | 3 | 4 | import android.animation.Animator; 5 | import android.animation.AnimatorSet; 6 | import android.animation.ObjectAnimator; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.os.Bundle; 10 | import android.os.Handler; 11 | import android.os.HandlerThread; 12 | import android.util.Log; 13 | import android.view.View; 14 | import android.view.Window; 15 | import android.view.WindowManager; 16 | import android.view.animation.DecelerateInterpolator; 17 | import android.widget.ImageView; 18 | import android.widget.TextView; 19 | import android.widget.Toast; 20 | 21 | import androidx.appcompat.app.AppCompatActivity; 22 | 23 | import com.tencent.xbright.lebwebrtcdemo.utils.AsyncHttpURLConnection; 24 | import com.tencent.xbright.lebwebrtcdemo.R; 25 | import com.tencent.xbright.lebwebrtcsdk.LEBWebRTCParameters.Loggable; 26 | import com.tencent.xbright.lebwebrtcsdk.LEBWebRTCView; 27 | import com.tencent.xbright.lebwebrtcsdk.LEBWebRTCStatsReport; 28 | import com.tencent.xbright.lebwebrtcsdk.LEBWebRTCEvents; 29 | import com.tencent.xbright.lebwebrtcsdk.LEBWebRTCTextureView; 30 | import com.tencent.xbright.lebwebrtcsdk.LEBWebRTCParameters; 31 | 32 | import org.json.JSONException; 33 | import org.json.JSONObject; 34 | 35 | import java.nio.ByteBuffer; 36 | 37 | import static com.tencent.xbright.lebwebrtcsdk.LEBWebRTCView.SCALE_IGNORE_ASPECT_FILL; 38 | import static com.tencent.xbright.lebwebrtcsdk.LEBWebRTCView.SCALE_KEEP_ASPECT_CROP; 39 | import static com.tencent.xbright.lebwebrtcsdk.LEBWebRTCView.SCALE_KEEP_ASPECT_FIT; 40 | 41 | /** 42 | * LEB WebRTC Demo Activity 43 | * weifei@tencent.com 44 | * 2020.4.8 45 | * 46 | * 注意:本demo只演示了快直播的拉流和停流的流程,没有实现业务和app本身的逻辑,比如: 47 | * 1. Surface相关,前后台切换、全屏缩放、屏幕旋转等逻辑。 48 | * 2. Audio相关,音频设备检测、请求和释放音频焦点等。 49 | * 3. 播放相关,完善pause、resume、stop等相关逻辑。 50 | */ 51 | 52 | public class LEBWebRTCDemoActivity extends AppCompatActivity implements LEBWebRTCEvents { 53 | private static final String TAG = "WebRTCDemoActivity"; 54 | private static final boolean USE_SURFACEVIEW = true; 55 | private DemoActivityParameter mParameter; 56 | private LEBWebRTCParameters mLEBWebRTCParameters; 57 | private LEBWebRTCView mWebRTCView; 58 | private TextView mStatsView; 59 | private ImageView mSnapshotView; 60 | private View mFeatureControlContainerView; 61 | 62 | private Animator mAnimator; 63 | 64 | private boolean mShowStats = true; 65 | private String mSvrSig;//服务器签名,后面每个请求必须携带这个字段内容, 业务无需理解字段内容 66 | 67 | private int mRotationDegree = 0; 68 | private int mScaleType = SCALE_KEEP_ASPECT_FIT; 69 | private HandlerThread mHandlerThread; 70 | private Handler mEventHandler; 71 | 72 | public static void start(Context context, DemoActivityParameter parameter) { 73 | Intent intent = new Intent(context, LEBWebRTCDemoActivity.class); 74 | intent.putExtra("param", parameter); 75 | context.startActivity(intent); 76 | } 77 | 78 | @Override 79 | protected void onCreate(Bundle savedInstanceState) { 80 | Log.d(TAG, "onCreate"); 81 | makeFullScreen(); 82 | super.onCreate(savedInstanceState); 83 | setContentView(R.layout.activity_webrtc_demo); 84 | 85 | mStatsView = findViewById(R.id.id_stats); 86 | mSnapshotView = findViewById(R.id.id_snapshot); 87 | setupFeatureControl(); 88 | 89 | 90 | mParameter = getIntent().getParcelableExtra("param"); 91 | if (mParameter == null) { 92 | Log.e(TAG, "no parameter found"); 93 | finish(); 94 | return; 95 | } 96 | 97 | Log.d(TAG, "encryption: " + mParameter.mEncryption + 98 | " hwDecode: " + mParameter.mEnableHwDecode + 99 | " receiveAudio: " + mParameter.mReceiveAudio + 100 | " receiveVideo: " + mParameter.mReceiveVideo + 101 | " seiCallback: " + mParameter.mSEICallback + 102 | " audioFormat: " + mParameter.mAudioFormat + 103 | " minJitterDelay: " + mParameter.mMinJitterDelayMs + 104 | " streamUrl: " + mParameter.mPlaybackStreamUrl + 105 | " pullUrl: " + mParameter.mPlaybackPullUrl); 106 | mLEBWebRTCParameters = new LEBWebRTCParameters(); 107 | mLEBWebRTCParameters.setStreamUrl(mParameter.mPlaybackStreamUrl); 108 | mLEBWebRTCParameters.setLoggingSeverity(LEBWebRTCParameters.LOG_VERBOSE); 109 | mLEBWebRTCParameters.setLoggable(LogCallback.getInstance()); 110 | mLEBWebRTCParameters.disableEncryption(!mParameter.mEncryption); 111 | mLEBWebRTCParameters.enableHwDecode(mParameter.mEnableHwDecode); 112 | mLEBWebRTCParameters.enableReceiveAudio(mParameter.mReceiveAudio); 113 | mLEBWebRTCParameters.enableReceiveVideo(mParameter.mReceiveVideo); 114 | mLEBWebRTCParameters.enableSEICallback(mParameter.mSEICallback); 115 | mLEBWebRTCParameters.setAudioFormat(mParameter.mAudioFormat); 116 | mLEBWebRTCParameters.setConnectionTimeOutInMs(5000);//5s 117 | mLEBWebRTCParameters.setStatsReportPeriodInMs(1000); 118 | mLEBWebRTCParameters.setMinJitterDelayMs(mParameter.mMinJitterDelayMs); 119 | mLEBWebRTCParameters.enableAudioJitterBufferFastAccelerate(true); 120 | 121 | if (USE_SURFACEVIEW) { 122 | mWebRTCView = findViewById(R.id.id_surface_view); 123 | } else { 124 | LEBWebRTCTextureView webRTCTextureView = findViewById(R.id.id_texture_view); 125 | webRTCTextureView.setVisibility(View.VISIBLE); 126 | mWebRTCView = webRTCTextureView; 127 | } 128 | mHandlerThread = new HandlerThread("EventThread"); 129 | mHandlerThread.start(); 130 | mEventHandler = new Handler(mHandlerThread.getLooper()); 131 | mWebRTCView.initilize(mLEBWebRTCParameters, this, mEventHandler); 132 | mWebRTCView.setScaleType(mScaleType); 133 | } 134 | 135 | @Override 136 | protected void onResume() { 137 | Log.v(TAG, "onResume"); 138 | super.onResume(); 139 | mWebRTCView.startPlay(); 140 | } 141 | 142 | @Override 143 | protected void onPause() { 144 | Log.v(TAG, "onPause"); 145 | super.onPause(); 146 | mWebRTCView.pausePlay(); 147 | } 148 | 149 | @Override 150 | protected void onStop() { 151 | Log.v(TAG, "onStop"); 152 | super.onStop(); 153 | mWebRTCView.stopPlay(); 154 | // 可以不调signalStop(), 后台在连接断开后会保底停止下发数据和计费 155 | signalingStop(); 156 | } 157 | 158 | @Override 159 | protected void onDestroy() { 160 | Log.v(TAG, "onDestroy"); 161 | super.onDestroy(); 162 | mWebRTCView.release(); 163 | mHandlerThread.quit(); 164 | } 165 | 166 | private void makeFullScreen() { 167 | requestWindowFeature(Window.FEATURE_NO_TITLE); 168 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 169 | WindowManager.LayoutParams.FLAG_FULLSCREEN); 170 | } 171 | 172 | @Override 173 | public void onEventOfferCreated(String sdp) { 174 | Log.v(TAG, "LEBWebRTC onEventOfferCreated"); 175 | signalingStart(sdp); 176 | } 177 | 178 | @Override 179 | public void onEventConnected() { 180 | Log.v(TAG, "LEBWebRTC onEventConnected"); 181 | } 182 | 183 | @Override 184 | public void onEventConnectFailed(ConnectionState state) { 185 | Log.v(TAG, "LEBWebRTC onEventConnectFailed, state: " + state); 186 | runOnUiThread(() -> { 187 | Toast.makeText(this, "连接失败", Toast.LENGTH_SHORT) 188 | .show(); 189 | }); 190 | } 191 | 192 | @Override 193 | public void onEventDisconnect() { 194 | Log.v(TAG, "LEBWebRTC onEventDisconnect"); 195 | runOnUiThread(() -> { 196 | Toast.makeText(this, "连接断开", Toast.LENGTH_SHORT) 197 | .show(); 198 | }); 199 | } 200 | 201 | @Override 202 | public void onEventFirstPacketReceived(int mediaType) { 203 | Log.v(TAG, "LEBWebRTC onEventFirstPacketReceived " + mediaType); 204 | } 205 | 206 | @Override 207 | public void onEventFirstFrameRendered() { 208 | Log.v(TAG, "LEBWebRTC onEventFirstFrameRendered"); 209 | } 210 | 211 | @Override 212 | public void onEventResolutionChanged(int width, int height) { 213 | Log.v(TAG, "LEBWebRTC onEventResolutionChanged, width " + width + " height " + height); 214 | runOnUiThread(() -> { 215 | Toast.makeText(this, "视频分辨率:" + width + "x" + height, Toast.LENGTH_SHORT) 216 | .show(); 217 | }); 218 | } 219 | 220 | @Override 221 | public void onEventSEIReceived(ByteBuffer data) {// 解码线程,不要阻塞,没有start code 222 | byte[] sei = new byte[data.capacity()]; 223 | data.get(sei); 224 | Log.v(TAG, "onEventSEIReceived: nal_type " + (sei[0]&0x1f) + " size " + sei.length); 225 | } 226 | 227 | @Override 228 | public void onEventVideoDecoderStart() { 229 | runOnUiThread(() -> { 230 | Toast.makeText(this, "视频解码器启动成功", Toast.LENGTH_SHORT).show(); 231 | }); 232 | } 233 | 234 | @Override 235 | public void onEventVideoDecoderFailed() { 236 | runOnUiThread(() -> { 237 | Toast.makeText(this, "视频解码失败", Toast.LENGTH_SHORT).show(); 238 | }); 239 | } 240 | 241 | @Override 242 | public void onEventVideoDecoderFallback() { 243 | runOnUiThread(() -> { 244 | Toast.makeText(this, "视频硬解失败,切换到软解", Toast.LENGTH_SHORT).show(); 245 | }); 246 | } 247 | 248 | @Override 249 | public void onEventStatsReport(LEBWebRTCStatsReport statsReport) { 250 | if (mShowStats) { 251 | runOnUiThread(() -> { 252 | String stats = 253 | "disableEncryption: " + mLEBWebRTCParameters.isDisableEncryption() + "\n" + 254 | "AudioFormat: " + mLEBWebRTCParameters.getAudioFormat() + "\n" + 255 | statsReport; 256 | mStatsView.setText(stats); 257 | //Log.d(TAG, "perf stats: " + stats); 258 | }); 259 | } 260 | } 261 | 262 | // Put a |key|->|value| mapping in |json|. 263 | private void jsonPut(JSONObject json, String key, Object value) { 264 | try { 265 | json.put(key, value); 266 | } catch (JSONException e) { 267 | throw new RuntimeException(e); 268 | } 269 | } 270 | 271 | // 向信令服务器发送offer,获取remote sdp, 通过setRemoteSDP接口设置给sdk 272 | private void signalingStart(final String sdp) { 273 | JSONObject jsonObject = new JSONObject(); 274 | JSONObject lsdp = new JSONObject(); 275 | jsonPut(lsdp,"sdp", sdp); 276 | jsonPut(lsdp,"type", "offer"); 277 | jsonPut(jsonObject,"localsdp", lsdp); 278 | jsonPut(jsonObject, "sessionid", "xxxxxx");//业务生成的唯一key, 标识本次拉流, 用户可自定义 279 | jsonPut(jsonObject, "clientinfo", "xxxxxx");//终端类型信息, 用户可自定义 280 | jsonPut(jsonObject, "streamurl", mLEBWebRTCParameters.getStreamUrl()); 281 | Log.d(TAG, "Connecting to signaling server: " + mParameter.mPlaybackPullUrl); 282 | Log.d(TAG, "Post data: " + jsonObject.toString()); 283 | Log.d(TAG, "send offer sdp: " + sdp); 284 | AsyncHttpURLConnection httpConnection = 285 | new AsyncHttpURLConnection("POST", mParameter.mPlaybackPullUrl, jsonObject.toString(), 286 | "origin url", "application/json", new AsyncHttpURLConnection.AsyncHttpEvents() { 287 | @Override 288 | public void onHttpError(String errorMessage) { 289 | Log.e(TAG, "connection error: " + errorMessage); 290 | //events.onSignalingParametersError(errorMessage); 291 | } 292 | 293 | @Override 294 | public void onHttpComplete(String response) { 295 | Log.e(TAG, response); 296 | try { 297 | JSONObject json = new JSONObject(response); 298 | int errcode = json.optInt("errcode"); 299 | String errmsg = json.optString("errmsg");; 300 | mSvrSig = json.optString("svrsig"); 301 | JSONObject rsdp = new JSONObject(json.optString("remotesdp")); 302 | String type = rsdp.optString("type"); 303 | String sdp = (rsdp.optString("sdp")); 304 | Log.d(TAG, "response from signaling server: " + response); 305 | Log.d(TAG, "svrsig info: " + mSvrSig); 306 | if (errcode == 0 && type.equals("answer") && sdp.length() > 0) { 307 | Log.d(TAG, "answer sdp = " + sdp); 308 | mWebRTCView.setRemoteSDP(sdp); 309 | } else if (errcode != 0){ 310 | Log.e(TAG, "signal respose error: " + errmsg); 311 | } 312 | } catch (JSONException e) { 313 | Log.d(TAG, "response JSON parsing error: " + e.toString()); 314 | } 315 | } 316 | }); 317 | httpConnection.send(); 318 | } 319 | 320 | //向信令服务器请求停流 321 | private void signalingStop() { 322 | JSONObject jsonObject = new JSONObject(); 323 | jsonPut(jsonObject,"streamurl", mLEBWebRTCParameters.getStreamUrl()); 324 | jsonPut(jsonObject,"svrsig", mSvrSig); 325 | AsyncHttpURLConnection httpConnection = 326 | new AsyncHttpURLConnection("POST", mParameter.mPlaybackStopUrl, jsonObject.toString(), 327 | "origin url", "application/json", new AsyncHttpURLConnection.AsyncHttpEvents() { 328 | @Override 329 | public void onHttpError(String errorMessage) { 330 | Log.e(TAG, "connection error: " + errorMessage); 331 | //events.onSignalingParametersError(errorMessage); 332 | } 333 | 334 | @Override 335 | public void onHttpComplete(String response) { 336 | try { 337 | JSONObject json = new JSONObject(response); 338 | int errcode = json.optInt("errcode"); 339 | String errMsg = json.optString("errmsg"); 340 | Log.d(TAG, "response from signling server: " + response); 341 | if (errcode == 0) { 342 | Log.d(TAG,"request to stop success"); 343 | } 344 | } catch (JSONException e) { 345 | Log.d(TAG, "response JSON parsing error: " + e.toString()); 346 | } 347 | } 348 | }); 349 | httpConnection.send(); 350 | } 351 | 352 | private void setupFeatureControl() { 353 | mFeatureControlContainerView = findViewById(R.id.id_feature_container); 354 | View snapShotBtn = mFeatureControlContainerView.findViewById(R.id.id_snapshot_button); 355 | snapShotBtn.setOnClickListener(view -> { 356 | takeSnapshot(); 357 | }); 358 | 359 | View rotationBtn = mFeatureControlContainerView.findViewById(R.id.id_rotation_button); 360 | rotationBtn.setOnClickListener(view -> { 361 | mRotationDegree = (mRotationDegree + 90) % 360; 362 | mWebRTCView.setVideoRotation(mRotationDegree); 363 | }); 364 | 365 | TextView scaleBtn = mFeatureControlContainerView.findViewById(R.id.id_scale_type_button); 366 | scaleBtn.setOnClickListener(view -> { 367 | if (mScaleType == SCALE_KEEP_ASPECT_FIT) { 368 | mScaleType = SCALE_IGNORE_ASPECT_FILL; 369 | scaleBtn.setText(R.string.scale_fill); 370 | } else if (mScaleType == SCALE_IGNORE_ASPECT_FILL) { 371 | mScaleType = SCALE_KEEP_ASPECT_CROP; 372 | scaleBtn.setText(R.string.scale_crop); 373 | } else { 374 | mScaleType = SCALE_KEEP_ASPECT_FIT; 375 | scaleBtn.setText(R.string.scale_fit); 376 | } 377 | mWebRTCView.setScaleType(mScaleType); 378 | }); 379 | 380 | TextView resetBtn = mFeatureControlContainerView.findViewById(R.id.id_restart); 381 | resetBtn.setOnClickListener(view -> { 382 | mWebRTCView.stopPlay(); 383 | mWebRTCView.startPlay(); 384 | }); 385 | } 386 | 387 | private void takeSnapshot() { 388 | mWebRTCView.takeSnapshot(bitmap -> { 389 | if (mAnimator != null) { 390 | mAnimator.cancel(); 391 | } 392 | 393 | mSnapshotView.setImageBitmap(bitmap); 394 | mSnapshotView.setVisibility(View.VISIBLE); 395 | mSnapshotView.setTranslationY(0); 396 | mSnapshotView.setScaleX(1.0f); 397 | mSnapshotView.setScaleY(1.0f); 398 | 399 | float scale = 0.4f; 400 | float y = mFeatureControlContainerView.getY() - mSnapshotView.getHeight(); 401 | ObjectAnimator down = ObjectAnimator.ofFloat(mSnapshotView, "y", y); 402 | ObjectAnimator scaleX = ObjectAnimator.ofFloat(mSnapshotView, "scaleX", scale); 403 | ObjectAnimator scaleY = ObjectAnimator.ofFloat(mSnapshotView, "scaleY", scale); 404 | AnimatorSet animatorSet = new AnimatorSet(); 405 | animatorSet.playTogether(down, scaleX, scaleY); 406 | animatorSet.playTogether(down); 407 | animatorSet.addListener(new Animator.AnimatorListener() { 408 | @Override 409 | public void onAnimationStart(Animator animation) { 410 | 411 | } 412 | 413 | @Override 414 | public void onAnimationEnd(Animator animation) { 415 | mSnapshotView.setVisibility(View.INVISIBLE); 416 | } 417 | 418 | @Override 419 | public void onAnimationCancel(Animator animation) { 420 | 421 | } 422 | 423 | @Override 424 | public void onAnimationRepeat(Animator animation) { 425 | 426 | } 427 | }); 428 | animatorSet.setDuration(1000); 429 | animatorSet.setInterpolator(new DecelerateInterpolator(1)); 430 | animatorSet.start(); 431 | mAnimator = animatorSet; 432 | }, 1.0f); 433 | } 434 | 435 | private static class LogCallback implements Loggable { 436 | private static final Loggable sInstance = new LogCallback(); 437 | 438 | public static Loggable getInstance() { 439 | return sInstance; 440 | } 441 | 442 | @Override 443 | public void onLogMessage(String tag, int level, String message) { 444 | 445 | final String t = "[lebwebrtc]" + tag; 446 | switch (level) { 447 | case LEBWebRTCParameters.LOG_VERBOSE: 448 | Log.v(t, message); 449 | break; 450 | case LEBWebRTCParameters.LOG_INFO: 451 | Log.i(t, message); 452 | break; 453 | case LEBWebRTCParameters.LOG_WARNING: 454 | Log.w(t, message); 455 | break; 456 | case LEBWebRTCParameters.LOG_ERROR: 457 | Log.e(t, message); 458 | break; 459 | default: 460 | Log.i(t, message); 461 | break; 462 | } 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /app/src/main/java/com/tencent/xbright/lebwebrtcdemo/utils/AsyncHttpURLConnection.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The WebRTC Project Authors. All rights reserved. 3 | * 4 | * Use of this source code is governed by a BSD-style license 5 | * that can be found in the LICENSE file in the root of the source 6 | * tree. An additional intellectual property rights grant can be found 7 | * in the file PATENTS. All contributing project authors may 8 | * be found in the AUTHORS file in the root of the source tree. 9 | */ 10 | 11 | package com.tencent.xbright.lebwebrtcdemo.utils; 12 | 13 | import android.util.Log; 14 | 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.OutputStream; 18 | import java.net.HttpURLConnection; 19 | import java.net.SocketTimeoutException; 20 | import java.net.URL; 21 | import java.util.Scanner; 22 | 23 | /** 24 | * Asynchronous http requests implementation. 25 | */ 26 | public class AsyncHttpURLConnection { 27 | private static final int HTTP_TIMEOUT_MS = 8000; 28 | private static final String HTTP_ORIGIN = "https://appr.tc"; 29 | private final String method; 30 | private final String url; 31 | private final String message; 32 | private final String origin; 33 | private final AsyncHttpEvents events; 34 | private String contentType; 35 | 36 | /** 37 | * Http requests callbacks. 38 | */ 39 | public interface AsyncHttpEvents { 40 | void onHttpError(String errorMessage); 41 | void onHttpComplete(String response); 42 | } 43 | 44 | public AsyncHttpURLConnection(String method, String url, String message, AsyncHttpEvents events) { 45 | this.method = method; 46 | this.url = url; 47 | this.message = message; 48 | this.events = events; 49 | this.origin = HTTP_ORIGIN; 50 | } 51 | 52 | public AsyncHttpURLConnection(String method, String url, String message, String origin, String ctype, AsyncHttpEvents events) { 53 | this.method = method; 54 | this.url = url; 55 | this.message = message; 56 | this.events = events; 57 | this.origin = origin; 58 | this.contentType = ctype; 59 | } 60 | 61 | public void setContentType(String contentType) { 62 | this.contentType = contentType; 63 | } 64 | 65 | public void send() { 66 | new Thread(this ::sendHttpMessage).start(); 67 | } 68 | 69 | private void sendHttpMessage() { 70 | try { 71 | HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); 72 | byte[] postData = new byte[0]; 73 | if (message != null) { 74 | postData = message.getBytes("UTF-8"); 75 | } 76 | connection.setRequestMethod(method); 77 | connection.setUseCaches(false); 78 | connection.setDoInput(true); 79 | connection.setConnectTimeout(HTTP_TIMEOUT_MS); 80 | connection.setReadTimeout(HTTP_TIMEOUT_MS); 81 | // TODO(glaznev) - query request origin from pref_room_server_url_key preferences. 82 | connection.addRequestProperty("origin", origin); 83 | boolean doOutput = false; 84 | if (method.equals("POST")) { 85 | doOutput = true; 86 | connection.setDoOutput(true); 87 | connection.setFixedLengthStreamingMode(postData.length); 88 | } 89 | if (contentType == null) { 90 | connection.setRequestProperty("Content-Type", "text/plain; charset=utf-8"); 91 | } else { 92 | connection.setRequestProperty("Content-Type", contentType); 93 | } 94 | 95 | // Send POST request. 96 | if (doOutput && postData.length > 0) { 97 | OutputStream outStream = connection.getOutputStream(); 98 | outStream.write(postData); 99 | outStream.close(); 100 | Log.d("HTTP POST", message); 101 | } 102 | 103 | // Get response. 104 | int responseCode = connection.getResponseCode(); 105 | if (responseCode != 200) { 106 | events.onHttpError("Non-200 response to " + method + " to URL: " + url + " : " 107 | + connection.getHeaderField(null)); 108 | connection.disconnect(); 109 | return; 110 | } 111 | InputStream responseStream = connection.getInputStream(); 112 | String response = drainStream(responseStream); 113 | responseStream.close(); 114 | connection.disconnect(); 115 | events.onHttpComplete(response); 116 | } catch (SocketTimeoutException e) { 117 | events.onHttpError("HTTP " + method + " to " + url + " timeout"); 118 | } catch (IOException e) { 119 | events.onHttpError("HTTP " + method + " to " + url + " error: " + e.getMessage()); 120 | } 121 | } 122 | 123 | // Return the contents of an InputStream as a String. 124 | private static String drainStream(InputStream in) { 125 | Scanner s = new Scanner(in, "UTF-8").useDelimiter("\\A"); 126 | return s.hasNext() ? s.next() : ""; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/cloud_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-hdpi/cloud_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-hdpi/disconnect.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-hdpi/ic_action_full_screen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_action_return_from_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-hdpi/ic_action_return_from_full_screen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_loopback_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-hdpi/ic_loopback_call.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-ldpi/disconnect.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_action_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-ldpi/ic_action_full_screen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_action_return_from_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-ldpi/ic_action_return_from_full_screen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/ic_loopback_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-ldpi/ic_loopback_call.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-mdpi/disconnect.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-mdpi/ic_action_full_screen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_action_return_from_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-mdpi/ic_action_return_from_full_screen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_loopback_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-mdpi/ic_loopback_call.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-xhdpi/disconnect.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-xhdpi/ic_action_full_screen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_action_return_from_full_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-xhdpi/ic_action_return_from_full_screen.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_loopback_call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/app/src/main/res/drawable-xhdpi/ic_loopback_call.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_call.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 17 | 18 | 24 | 25 | 29 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 22 | 23 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_webrtc_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 33 | 34 | 47 | 48 | 59 | 60 | 68 | 77 | 78 | 91 | 92 | 106 | 107 | 121 | 122 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_webrtc_push_demo.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 31 | 32 | 45 | 46 | 57 | 58 | 66 | 75 | 76 | 89 | 90 | 104 | 105 | 119 | 120 | 135 | 136 | 151 | 152 | 153 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_playback_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 26 | 27 | 34 | 35 | 42 | 43 | 52 | 53 | 58 | 59 | 65 | 75 | 76 | 85 | 86 | 95 | 96 | 97 | 102 | 103 | 109 | 119 | 120 | 129 | 130 | 139 | 140 | 141 | 142 | 151 | 152 | 161 | 162 | 167 | 174 | 175 |