├── .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 |
182 |
183 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_publish_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
23 |
24 |
32 |
33 |
44 |
45 |
53 |
54 |
66 |
67 |
78 |
79 |
88 |
89 |
97 |
106 |
107 |
116 |
117 |
126 |
127 |
136 |
137 |
138 |
149 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/simple_playback_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
25 |
26 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/playback_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/navigation/main_navigation.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v17/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - Default
5 | - 4K (3840 x 2160)
6 | - Full HD (1920 x 1080)
7 | - HD (1280 x 720)
8 | - VGA (640 x 480)
9 | - QVGA (320 x 240)
10 |
11 |
12 |
13 | - Default
14 | - 3840 x 2160
15 | - 1920 x 1080
16 | - 1280 x 720
17 | - 640 x 480
18 | - 320 x 240
19 |
20 |
21 |
22 | - Default
23 | - 30 fps
24 | - 15 fps
25 |
26 |
27 |
28 | - Default
29 | - Manual
30 |
31 |
32 |
33 | - H264 Baseline
34 | - H264 High
35 |
36 |
37 |
38 | - OPUS
39 | - ISAC
40 |
41 |
42 |
43 | - Auto (proximity sensor)
44 | - Enabled
45 | - Disabled
46 |
47 |
48 |
49 | - auto
50 | - true
51 | - false
52 |
53 |
54 |
55 | - Remove favorite
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 20dp
4 | 10dp
5 | 16sp
6 | 10sp
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 腾讯云快直播Demo
4 | 截屏
5 | 旋转
6 | 填充缩放
7 | 等比例缩放
8 | 等比例裁剪
9 | 重启
10 |
11 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.0'
11 | classpath 'com.kezong:fat-aar:1.2.7'
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 |
22 | }
23 | }
24 |
25 | task clean(type: Delete) {
26 | delete rootProject.buildDir
27 | }
28 |
29 | ext {
30 | minSdkVersion = 16
31 | compileSdkVersion = 29
32 | targetSdkVersion = 29
33 |
34 | releaseVersionCode = 1
35 | releaseVersionName = '1.0.0'
36 | }
--------------------------------------------------------------------------------
/docs/Changelog.md:
--------------------------------------------------------------------------------
1 | ## leb android sdk release history
2 |
3 | ### v2.0.9
4 | 1. 增加最小jitter delay限制接口
5 | 2. 修复一些问题
6 |
7 | ### v2.0.8
8 | 1. 支持audio only and video only播放模式
9 | 2. 增加视频解码器状态回调接
10 | 3. 整理demo代码
11 |
12 | ### v2.0.5
13 | 1. 增加最大音频JitterBuffer队列大小设置接口
14 | 2. 增加追帧功能开关接口
15 |
16 | ### v2.0.4
17 | 1. 修复不设置日志回调时crash问题
18 |
19 | ### v2.0.3
20 | 1. 重构接口, LEBWebRTCView变为接口类,增加LEBWebRTCSurfaceView和LEBWebRTCTextureView
21 | 2. 支持日志回调
22 | 3. 修复切后台再切前台黑屏问题
23 |
24 | ### v2.0.2
25 | 1. 支持画面截图
26 | 2. 支持旋转显示
27 | 3. 支持等比例缩放和平铺缩放
28 | 4. 支持音频增益设置
29 |
30 | ### v2.0.1
31 | 扩展标准WebRTC,支持如下特性:
32 | 1. 支持AAC,包括LC、HE和HEv2
33 | 2. 支持H265,包括硬解和软解
34 | 3. 支持H264和H265的B帧解码
35 | 4. 支持关闭加密
36 | 5. 支持H264和H265的SEI数据回调
37 |
38 | ### v1.0.7
39 | 1. support builtin h264 software decoder
40 |
41 | ### v1.0.6
42 | 1. clear frame when stop play
43 | 2. polish stats info, add delay, jiterbufferdelay, nackssent and rtt
44 |
45 | ### v1.0.5
46 | 1. add audio jitter buffer config
47 |
48 | ### v1.0.4
49 | 1. force to use platform software decoder when hwdecode is not enabled
50 |
51 | ### v1.0.3
52 | 1. fix a problem for multi-instances
53 | 2. add api for playing in mute
54 |
55 | ### v1.0.2
56 | 1. fix a build problem
57 |
58 | ### v1.0.1
59 | 1. update document
60 |
61 | ### v1.0.0
62 | 1. init sdk and demo
63 |
--------------------------------------------------------------------------------
/docs/leb_android_sdk.md:
--------------------------------------------------------------------------------
1 |
2 | # 快直播LEB WebRTC Android SDK接入文档
3 |
4 | ## 1. SDK简介
5 | 快直播(Live Event Broadingcasting)腾讯基于WebRTC技术的超低延时直播,通过LEBWebRTC Android SDK,接入商只需对接几个接口,快速实现Android平台实现接入和播放。
6 |
7 | ## 2. SDK集成接入
8 | 注意:本demo只演示了快直播的拉流和停流的流程,没有实现其他业务和app本身的逻辑,比如:
9 | 1. Surface相关,前后台切换、全屏缩放、屏幕旋转等逻辑。
10 | 2. Audio相关,音频设备检测、请求和释放音频焦点等。
11 | 3. 播放相关,完善pause、resume、stop等相关逻辑。
12 |
13 | ### 2.1 jcenter接入方式
14 | SDK提供两种⽅方式接⼊入: jcenter 和 AAR ,可以根据需要选择接⼊入⽅方式,分别如下:
15 | 首先,在app的最上层 build.gradle 中加⼊入 jcenter的仓库依赖
16 | buildscript {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 | 然后,在相关module的build.gradle中加⼊入依赖
22 | dependencies {
23 | implementation 'com.tencent.xbright:lebwebrtcsdk:2.0.5'
24 | }
25 |
26 | ### 2.3 SO库的ABI说明
27 | SDK内包含SO库,⽬前仅⽀支持 armeabi-v7a、 arm64-v8a 两种ABI架构。
28 |
29 | ### 2.4 权限
30 | 需要配置⼀一些必要的权限才能正常运⾏行行,请确认app的 AndroidManifest.xml 中添加了了如下权限:
31 |
32 |
33 |
34 |
35 |
36 |
37 | ### 2.5 渲染硬件加速
38 | 如需提⾼高显示渲染效率,可以在 AndroidManifest.xml 中将硬件加速打开
39 |
40 |
41 | ## 3. SDK接口
42 | SDK封装类为LEBWebRTCView
43 | public interface LEBWebRTCView extends VideoSink {
44 | // 保留视频宽高比
45 | int SCALE_KEEP_ASPECT_FIT = 0;
46 | // 填充view,不保留宽高比
47 | int SCALE_IGNORE_ASPECT_FILL = 1;
48 | //初始化sdk
49 | void initilize(@NonNull LEBWebRTCParameters rtcParams, @NonNull LEBWebRTCEvents rtcEvents);
50 | //获取context
51 | Context getContext();
52 | //设置remote sdp
53 | void setRemoteSDP(String sdp);
54 | //开始播放
55 | void startPlay();
56 | //暂停播放
57 | void pausePlay();
58 | //停止播放
59 | void stopPlay();
60 | //释放资源
61 | void release();
62 | //静音播放
63 | void mutePlay(boolean isMute);
64 | //设置PCM音量增益
65 | void setVolume(double volume);
66 | //设置画面旋转角度,90、180、270
67 | void setVideoRotation(int degree);
68 | //截图接口
69 | void takeSnapshot(@NonNull SnapshotListener listener, float scale);
70 | //设置缩放模式SCALE_KEEP_ASPECT_FIT和SCALE_IGNORE_ASPECT_FILL
71 | void setScaleType(int scaleType);
72 | //截图回调
73 | interface SnapshotListener {
74 | void onSnapshot(Bitmap bitmap);
75 | }
76 | }
77 | ### 3.1 初始化
78 | LEBWebRTCView mWebRTCView = findViewById(R.id.id_surface_view);
79 |
80 | mWebRTCView.initilize(LEBWebRTCParameters rtcParams, LEBWebRTCEvents rtcEvents)
81 |
82 | LEBWebRTCParameters为配置参数,具体见下文
83 | LEBWebRTCEvents为事件回调,具体见下文
84 |
85 | LEBWbRTCView构造见下面示例:
86 | LEBWebRTCView mWebRTCView = findViewById(R.id.id_surface_view);
87 | R.id.id_surface_view:
88 |
94 |
99 |
100 |
101 |
102 | LEBWebRTCParameters定义如下:
103 |
104 | public class LEBWebRTCParameters {
105 |
106 | // 快直播播放地址
107 | private String mStreamUrl;
108 | // 是否使用硬件解码
109 | private boolean mEnableHwDecode = true;
110 | // 音频格式
111 | public static final int OPUS = 0x01;
112 | public static final int AAC_LATM = 0x02;
113 | public static final int AAC_ADTS = 0x04;
114 | private int mAudioFormat = OPUS;
115 | // 是否关闭加密传输
116 | private boolean mDisableEncryption = false;
117 | // 是否启用SEI回调
118 | private boolean mEnableSEICallback = false;
119 | // 播放状态回调周期
120 | private int mStatsReportPeriodInMs = 1000;
121 | // WebRTC日志级别
122 | public static final int LOG_VERBOSE = 0x00;
123 | public static final int LOG_INFO = 0x01;
124 | public static final int LOG_WARNING = 0x02;
125 | public static final int LOG_ERROR = 0x03;
126 | public static final int LOG_NONE = 0x04;
127 | private int mLoggingSeverity = LOG_NONE;
128 | // WebRTC连接超时
129 | private int mConnectoionTimeoutInMs = 5000;//ms
130 |
131 | // 音频PCM增益,0~10.0,增益过大会使PCM过饱和
132 | private double mDefaultVolume = 1.0f;
133 | // 音频最大队列,会影响最大延时
134 | private int mAudioJitterBufferMaxPackets = 50;
135 | // 是否开启追帧功能
136 | private boolean mEnableAudioJitterBufferFastAccelerate = true;
137 | // 是否拉音频流
138 | private boolean mReceiveAudio = true;
139 | // 是否拉视频流
140 | private boolean mReceiveVideo = true;
141 | ...
142 | }
143 | LEBWebRTCParameters构造见下面示例:
144 |
145 | //创建参数对象
146 | LEBWebRTCParameters mLEBWebRTCParameters = new LEBWebRTCParameters();
147 | //设置播放码流链接, webrtc://xxxxx
148 | mLEBWebRTCParameters.setStreamUrl(mWebRTCUrl);
149 | //设置是否硬解,默认为硬解
150 | mLEBWebRTCParameters.enableHwDecode(mEnableHwDecode);
151 | //设置连接超时时间,默认为5000ms
152 | mLEBWebRTCParameters.setConnectionTimeOutInMs(5000);
153 | //设置播放状态回调事件周期,默认为1000ms
154 | mLEBWebRTCParameters.setStatsReportPeriodInMs(1000);
155 | //设置是否关闭加密,默认为打开加密
156 | mLEBWebRTCParameters.disableEncryption(mDisableEncryption);
157 | //设置是否启用SEI回调,默认为关闭
158 | mLEBWebRTCParameters.enableSEICallback(mEnableSEICallback);
159 | //设置最小JitterDelay,默认为1000ms,越大越能对抗网络抖动,按场景需要设置
160 | mLEBWebRTCParameters.setMinJitterDelayMs(1000);
161 | //设置是否开启追帧功能,默认开启
162 | mLEBWebRTCParameters.enableAudioJitterBufferFastAccelerate(true);
163 | //设置是否拉音频流,默认开启
164 | mLEBWebRTCParameters.enableReceiveAudio(true);
165 | //设置是否拉视频流,默认开启
166 | mLEBWebRTCParameters.enableReceiveVideo(true);
167 | //设置拉流音频格式,LEBWebRTCParameters.OPUS, LEBWebRTCParameters.AAC_LATM, LEBWebRTCParameters.AAC_ADTS
168 | mLEBWebRTCParameters.setAudioFormat(mAudioFormat);
169 | //设置日志级别,默认为LOG_NONE
170 | mLEBWebRTCParameters.setLoggingSeverity(LEBWebRTCParameters.LOG_NONE);
171 | //设置日志回调
172 | mLEBWebRTCParameters.setLoggable((String tag, int level, String message) -> {
173 | final String t = "[lebwebrtc]" + tag;
174 | switch (level) {
175 | case LEBWebRTCParameters.LOG_VERBOSE:
176 | Log.v(t, message);
177 | break;
178 | case LEBWebRTCParameters.LOG_INFO:
179 | Log.i(t, message);
180 | break;
181 | case LEBWebRTCParameters.LOG_WARNING:
182 | Log.w(t, message);
183 | break;
184 | case LEBWebRTCParameters.LOG_ERROR:
185 | Log.e(t, message);
186 | break;
187 | default:
188 | Log.i(t, message);
189 | break;
190 | }
191 | });
192 |
193 | LEBWebRTCEvents事件回调定义如下:
194 | public interface LEBWebRTCEvents {
195 | enum ConnectionState
196 | {
197 | // 开始建立连接
198 | STATE_BEGIN,
199 | // OFFER创建
200 | STATE_OFFER_CREATED,
201 | // ICE完成
202 | STATE_ICE_COMPLETED,
203 | // 连接建立
204 | STATE_WEBRTC_CONNECTED,
205 | // 渲染第一帧
206 | STATE_FIRST_FRAME_RENDERED,
207 | // 连接超时
208 | STATE_WEBRTC_TIMEOUT,
209 | }
210 | // offer创建成功
211 | void onEventOfferCreated(String sdp);
212 | // 连接成功
213 | void onEventConnected();
214 | // 连接失败
215 | void onEventConnectFailed(ConnectionState cs);
216 | // 连接断开
217 | void onEventDisconnect();
218 | // 收到首包数据
219 | void onEventFirstPacketReceived(int mediType);//0:audio, 1:video
220 | // 渲染首帧
221 | void onEventFirstFrameRendered();
222 | // 分辨率切换
223 | void onEventResolutionChanged(int width, int height);
224 | // 统计数据
225 | void onEventStatsReport(LEBWebRTCStatsReport webRTCStatsReport);
226 | // sei回调,解码线程,不要阻塞,没有start code
227 | void onEventSEIReceived(ByteBuffer data);
228 | }
229 | 其中onEventStatsReport(LEBWebRTCStatsReport webRTCStatsReport)用来回调播放状态,包含音视频播放性能、播放帧率、码率和时长等数据,LEBWebRTCStatsReport定义如下:
230 |
231 | public class LEBWebRTCStatsReport {
232 | //video stats
233 | public long mFirstVideoPacketDelayMs;//从启动到收到第一包视频数据的延时
234 | public long mFirstFrameRenderDelayMs; //从启动到首帧渲染延时
235 | public float mVideoDecodeFps; //当前解码帧率
236 | public float mVideoDecoderAvgFps;//平均帧率
237 | public float mVideoRenderFps; // 当前视频渲染帧率
238 | public long mVideoRenderReceived; // 视频渲染收到的帧数
239 | public long mVideoRenderDropped; // 渲染时丢弃的帧数
240 | public long mTotalFrozenTimeMs; // 总卡顿时长
241 | public float mFrozenRate; // 总卡顿时长/播放时长
242 | public long mVideoBitrate; //视频码率
243 | public long mFramesDecoded; //解码帧数
244 | public long mFramesDropped; //丢帧数
245 | public long mFramesReceived; //接收帧数
246 | public int mVideoPacketsLost; //丢包个数
247 | public long mVideoPacketsReceived; //接收包数
248 | public long mFrameWidth; //视频宽度
249 | public long mFrameHeight; //视频高度
250 | public long mVideoDelayMs;
251 | public long mVideoJitterBufferDelayMs;
252 | public long mVideoNacksSent;
253 | public long mRTT;
254 |
255 | //audio stats
256 | public long mFirstAudioPacketDelayMs;//从启动到收到第一包音频数据的延时
257 | public int mAudioPacketsLost; //丢包个数
258 | public long mAudioPacketsReceived; //接收包数
259 | public long mAudioBitrate;//音频码率
260 | public long mAudioDelayMs;
261 | public long mAudioJitterBufferDelayMs;
262 | public long mAudioNacksSent;
263 |
264 | //play stats
265 | public long mAverageBitRate;//平均码率
266 | public long mPlayTimeMs;//播放时长
267 | }
268 |
269 |
270 |
271 |
272 | ### 3.1 启动过程
273 | 初始化后开始启动过程,步骤如下
274 | #### 1. 开始启动sdk,sdk在p2p未连接时会创建offer(local sdp)
275 | void startPlay()
276 | #### 2. Offer创建成功后回调,用户可以回调中实现向信令服务器发送offer,获取remote sdp,并设置给SDK (信令请求具体见下文)
277 | void onEventOfferCreated(String sdp)
278 | #### 3. 将remote sdp设置给SDK,sdk会发起p2p连接,连接成功后开始播放
279 | void setRemoteSDP(String sdp)
280 |
281 | ### 3. 2 暂停播放
282 | //暂停播放,保持连接
283 | void pausePlay()
284 |
285 | ### 3.3 继续播放
286 | //在暂停后恢复播放
287 | void startPlay()
288 |
289 | ### 3.3 退出播放
290 | 退出播放需先向信令服务器发出请求(具体见下文)再本地执行下面命令
291 | //退出播放、并断开连接
292 | void stopPlay()
293 |
294 | ### 3.4 释放资源
295 | //释放SDK相关资源
296 | void release()
297 |
298 | ### 3.5 静音播放
299 | //设置静音播放
300 | void mutePlay(boolen isMute)
301 |
302 | ### 3.6 设置音频PCM增益,0~10.0,默认为1.0
303 | 注意:增益过大会使PCM过饱和
304 | void setVolume(double volume)
305 |
306 | ### 3.7 截取视频内容
307 | // 通过SnapshotListener回调输出bitmap,scale会截图缩放比例
308 | void takeSnapshot(@NonNull SnapshotListener listener, float scale)
309 |
310 | SnapshotListener 定义如下:
311 | public interface SnapshotListener {
312 | void onSnapshot(Bitmap bitmap);
313 | }
314 |
315 | ### 3.8 设置显示旋转角度,90,180,270
316 | void setVideoRotation(int degree)
317 |
318 | ### 3.9 显示缩放模式, 等比例缩放和平铺缩放
319 | // scaleType SCALE_KEEP_ASPECT_FIT, SCALE_IGNORE_ASPECT_FILL
320 | void setScaleType(int scaleType)
321 |
322 | ## 4. http信令
323 | http信令包括,拉流和停流,由用户在app侧实现,具体定义请见《signal_http_protoc》
324 | //请求拉流
325 | https://webrtc.liveplay.myqcloud.com/webrtc/v1/pullstream
326 | //停止拉流
327 | https://webrtc.liveplay.myqcloud.com/webrtc/v1/stopstream
328 |
329 |
330 | ### 4.1 拉流
331 |
332 | //请求
333 | {
334 | streamurl: string //拉流地址
335 | sessionid: string //业务生成的唯一key,标识本次拉流
336 | clientinfo: string //终端类型信息
337 | localsdp: {
338 | type: string
339 | sdp: string
340 | }
341 | clientip: string //客户端IP
342 | seipass: int //是否带SEI
343 | }
344 |
345 | //返回
346 | {
347 | errcode: int
348 | errmsg: string
349 | remotesdp: {
350 | type: string
351 | sdp: string
352 | }
353 | svrsig: string //服务器签名,后面每个请求必须携带这个字段内容,业务无需理解字段内容
354 | }
355 |
356 | ### 4.2 停流
357 | //请求
358 | {
359 | streamurl: string //拉流地址
360 | svrsig: string //pullstream返回的服务器签名
361 | }
362 |
363 | //返回
364 | {
365 | errcode: int
366 | errmsg: string
367 | }
368 |
369 | 停止拉流必须调用本接口通知后台停止拉流,否则后台会在数据通道超时断开前继续下发音视频数据,残留的UDP通道既浪费后台资源,也会影响业务并发和计费带宽统计
370 |
371 |
372 |
373 | ## 5. bugly接入
374 | 建议app接入bugly来监控运行质量,具体见bugly官方文档https://bugly.qq.com/docs/user-guide/instruction-manual-android/?v=20200622202242
375 |
376 | ### 5.1 build.gradle添加
377 |
378 | dependencies {
379 | compile 'com.tencent.bugly:crashreport:latest.release' //其中latest.release指代最新Bugly SDK版本号,也可以指定明确的版本号,例如2.1.9
380 | compile 'com.tencent.bugly:nativecrashreport:latest.release' //其中latest.release指代最新Bugly NDK版本号,也可以指定明确的版本号,例如3.0
381 | }
382 |
383 | ### 5.2 bugly初始化示例
384 |
385 | CrashReport.UserStrategy strategy = new CrashReport.UserStrategy(getApplicationContext());
386 | strategy.setAppChannel("myChannel"); //设置渠道
387 | strategy.setAppVersion("2.0.5"); //App的版本, 这里设置了SDK version
388 | strategy.setAppPackageName("com.tencent.xbright.lebwebrtcdemo"); //App的包名
389 | CrashReport.initCrashReport(getApplicationContext(), "e3243444c9", false, strategy);
390 |
391 |
392 | ## 6. 标准WebRTC扩展
393 | 快直播SDK和后台扩展了标准WebRTC,支持AAC、H265、B帧和关闭加密,其中使用AAC、H265和B帧的拉流需要后台配置拉流域名
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
--------------------------------------------------------------------------------
/docs/leb_signal_spec.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/docs/leb_signal_spec.pdf
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | org.gradle.jvmargs=-Xmx1536m
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. More details, visit
14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
15 | # org.gradle.parallel=true
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tencentyun/leb-android-sdk/e17403a31c9b17fcf091bac68b1e2e89cbcc4be9/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 26 14:33:39 CST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='leb_android_sdk'
3 |
--------------------------------------------------------------------------------