├── Agora-Online-PK-Android
├── .gitignore
├── LICENSE.md
├── app
│ ├── .gitignore
│ ├── build.gradle
│ ├── libs
│ │ └── PLACEHOLDER
│ ├── proguard-rules.pro
│ └── src
│ │ ├── androidTest
│ │ └── java
│ │ │ └── io
│ │ │ └── agora
│ │ │ └── pk
│ │ │ └── ExampleInstrumentedTest.java
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ │ └── io
│ │ │ │ └── agora
│ │ │ │ └── pk
│ │ │ │ ├── BaseActivity.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ ├── PKApplication.java
│ │ │ │ ├── PKBroadcasterActivity.java
│ │ │ │ ├── PKConfig.java
│ │ │ │ ├── model
│ │ │ │ ├── AGEventHandler.java
│ │ │ │ ├── MyEngineEventHandler.java
│ │ │ │ └── WorkerThread.java
│ │ │ │ ├── ui
│ │ │ │ └── CircleImageView.java
│ │ │ │ └── utils
│ │ │ │ ├── KeyboardStatusDetector.java
│ │ │ │ ├── MessageUtils.java
│ │ │ │ ├── PKConstants.java
│ │ │ │ └── StringUtils.java
│ │ ├── jniLibs
│ │ │ ├── arm64-v8a
│ │ │ │ └── PLACEHOLDER
│ │ │ ├── armeabi-v7a
│ │ │ │ └── PLACEHOLDER
│ │ │ └── x86
│ │ │ │ └── PLACEHOLDER
│ │ └── res
│ │ │ ├── drawable
│ │ │ ├── chat_room_et_style_2.xml
│ │ │ ├── chat_room_main_exit.png
│ │ │ ├── chat_room_main_like.png
│ │ │ ├── chat_room_main_pk_bg.xml
│ │ │ ├── chat_room_main_pk_support.png
│ │ │ ├── ic_launcher_background.xml
│ │ │ ├── ic_launcher_foreground.xml
│ │ │ ├── main_btn_style.xml
│ │ │ ├── pk_exit_btn_style.xml
│ │ │ ├── user_icon_01.png
│ │ │ ├── user_icon_02.png
│ │ │ ├── user_icon_03.png
│ │ │ ├── user_icon_04.png
│ │ │ ├── user_icon_05.png
│ │ │ ├── user_icon_06.png
│ │ │ ├── user_icon_07.png
│ │ │ └── user_icon_08.png
│ │ │ ├── layout
│ │ │ ├── activity_main.xml
│ │ │ ├── activity_pk_broadcaster.xml
│ │ │ └── pop_view_pk.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ │ └── values
│ │ │ ├── attrs.xml
│ │ │ ├── colors.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ │ └── test
│ │ └── java
│ │ └── io
│ │ └── agora
│ │ └── pk
│ │ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── tools
│ └── gradle-on-demand.gradle
├── Agora-Online-PK-iOS
├── .gitignore
├── Agora-Online-PK.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Agora-Online-PK
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── agoraLog.imageset
│ │ │ ├── Contents.json
│ │ │ ├── agoraLog.png
│ │ │ ├── agoraLog@2x.png
│ │ │ └── agoraLog@3x.png
│ │ ├── agoraLogText.imageset
│ │ │ ├── Contents.json
│ │ │ ├── agoraLogText.png
│ │ │ ├── agoraLogText@2x.png
│ │ │ └── agoraLogText@3x.png
│ │ ├── back.imageset
│ │ │ ├── Contents.json
│ │ │ └── back.png
│ │ ├── background_button.imageset
│ │ │ ├── Contents.json
│ │ │ ├── background_button.png
│ │ │ ├── background_button@2x.png
│ │ │ └── background_button@3x.png
│ │ ├── background_room.imageset
│ │ │ ├── Contents.json
│ │ │ ├── background_room.png
│ │ │ ├── background_room@2x.png
│ │ │ └── background_room@3x.png
│ │ ├── cancel.imageset
│ │ │ ├── Contents.json
│ │ │ ├── cancel.png
│ │ │ ├── cancel@2x.png
│ │ │ └── cancel@3x.png
│ │ ├── heart.imageset
│ │ │ ├── Contents.json
│ │ │ ├── heart.png
│ │ │ ├── heart@2x.png
│ │ │ └── heart@3x.png
│ │ ├── heart_room.imageset
│ │ │ ├── Contents.json
│ │ │ ├── heart_room.png
│ │ │ ├── heart_room@2x.png
│ │ │ └── heart_room@3x.png
│ │ ├── profile.imageset
│ │ │ ├── Contents.json
│ │ │ └── profile_06.png
│ │ └── user_login.imageset
│ │ │ ├── Contents.json
│ │ │ ├── user_login.png
│ │ │ ├── user_login@2x.png
│ │ │ └── user_login@3x.png
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Contants
│ │ ├── Constants.swift
│ │ └── KeyCenter.swift
│ ├── Controllers
│ │ ├── LoginViewController.swift
│ │ └── RoomViewController.swift
│ ├── Info.plist
│ ├── Utils
│ │ ├── AlertUtil.swift
│ │ └── CommonExtensions.swift
│ └── Views
│ │ ├── PopView.swift
│ │ ├── PopView.xib
│ │ └── VideoSession.swift
└── LICENSE.md
├── Image
├── API_list.png
├── API_list_EN.png
├── ArchitectureDesign.png
├── ArchitectureDesign_EN.png
└── descritpion.md
├── README.md
└── README.zh.md
/Agora-Online-PK-Android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | .DS_Store
8 | /build
9 | /captures
10 | .externalNativeBuild
11 | .idea/
12 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Agora.io
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 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 28
5 | defaultConfig {
6 | applicationId "io.agora.pk"
7 | minSdkVersion 16
8 | targetSdkVersion 28
9 | versionCode 1
10 | versionName "1.0"
11 |
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 |
14 | ndk {
15 | abiFilters "armeabi-v7a"
16 | }
17 | }
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 |
25 | flavorDimensions "default"
26 | productFlavors {
27 | all32 { minSdkVersion 16 }
28 | all64 { minSdkVersion 21 }
29 | }
30 |
31 | repositories {
32 | flatDir {
33 | dirs 'libs'
34 | }
35 | }
36 | }
37 |
38 | dependencies {
39 | implementation fileTree(include: ['*.jar'], dir: 'libs')
40 | implementation 'com.android.support:support-v4:28.0.0'
41 | implementation 'com.android.support:design:28.0.0'
42 | }
43 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/libs/PLACEHOLDER:
--------------------------------------------------------------------------------
1 | agora-rtc-sdk.jar
2 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/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 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/androidTest/java/io/agora/pk/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumented test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("io.agora.pk", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.Nullable;
5 | import android.support.v7.app.AppCompatActivity;
6 |
7 | import java.util.ArrayList;
8 | import java.util.HashMap;
9 |
10 | import io.agora.pk.model.MyEngineEventHandler;
11 | import io.agora.pk.model.WorkerThread;
12 | import io.agora.pk.utils.PKConstants;
13 | import io.agora.rtc.RtcEngine;
14 | import io.agora.rtc.live.LiveTranscoding;
15 |
16 | public abstract class BaseActivity extends AppCompatActivity {
17 | @Override
18 | protected void onCreate(@Nullable Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | ((PKApplication) getApplication()).initWorkerThread();
21 | }
22 |
23 | @Override
24 | protected void onPostCreate(@Nullable Bundle savedInstanceState) {
25 | super.onPostCreate(savedInstanceState);
26 | initUIandEvent();
27 | }
28 |
29 | protected abstract void initUIandEvent();
30 |
31 | protected abstract void deInitUIandEvent();
32 |
33 | protected RtcEngine rtcEngine() {
34 | return ((PKApplication) getApplication()).getWorkerThread().rtcEngine();
35 | }
36 |
37 | protected WorkerThread workThread() {
38 | return ((PKApplication) getApplication()).getWorkerThread();
39 | }
40 |
41 | protected final MyEngineEventHandler event() {
42 | return ((PKApplication) getApplication()).getWorkerThread().eventHandler();
43 | }
44 |
45 | // set LiveTranscoding property for each user
46 | protected LiveTranscoding updateLiveTranscoding(int localUid, int remoteUid, HashMap members) {
47 |
48 | ArrayList users = new ArrayList<>(members.size());
49 |
50 | LiveTranscoding.TranscodingUser localUser = new LiveTranscoding.TranscodingUser();
51 |
52 | LiveTranscoding liveTranscoding = new LiveTranscoding();
53 |
54 | // LiveTranscoding update, the LiveTranscoding is used to set the CDN stream layout in Agora server
55 | // more details please refer to the document
56 | switch (members.size()) {
57 | case 1:
58 | // the LiveTranscoding for one person
59 | localUser.uid = localUid;
60 |
61 | localUser.x = 0;
62 | localUser.y = 0;
63 | localUser.width = PKConstants.LIVE_TRANSCODING_WIDTH;
64 | localUser.height = PKConstants.LIVE_TRANSCODING_HEIGHT;
65 |
66 | localUser.zOrder = 1;
67 | localUser.audioChannel = 0;
68 |
69 | liveTranscoding.addUser(localUser);
70 |
71 | liveTranscoding.width = PKConstants.LIVE_TRANSCODING_WIDTH;
72 | liveTranscoding.height = PKConstants.LIVE_TRANSCODING_HEIGHT;
73 |
74 | liveTranscoding.videoBitrate = PKConstants.LIVE_TRANSCODING_BITRATE;
75 | liveTranscoding.videoFramerate = PKConstants.LIVE_TRANSCODING_FPS;
76 | liveTranscoding.lowLatency = true;
77 | break;
78 |
79 | case 2:
80 | // the LiveTranscoding for two persons in PK mode
81 | localUser.uid = localUid;
82 |
83 | localUser.x = 0;
84 | localUser.y = 0;
85 | localUser.width = PKConstants.LIVE_TRANSCODING_WIDTH;
86 | localUser.height = PKConstants.LIVE_TRANSCODING_HEIGHT;
87 |
88 | localUser.zOrder = 1;
89 | localUser.audioChannel = 0;
90 |
91 | users.add(localUser);
92 |
93 | LiveTranscoding.TranscodingUser remoteUser = new LiveTranscoding.TranscodingUser();
94 |
95 | remoteUser.uid = members.get(remoteUid); // REMOTE USER
96 |
97 | remoteUser.x = PKConstants.LIVE_TRANSCODING_WIDTH; // START FROM END OF THE FIRST USER
98 | remoteUser.y = 0;
99 | remoteUser.width = PKConstants.LIVE_TRANSCODING_WIDTH;
100 | remoteUser.height = PKConstants.LIVE_TRANSCODING_HEIGHT;
101 |
102 | remoteUser.zOrder = 1;
103 | remoteUser.audioChannel = 0;
104 |
105 | users.add(remoteUser);
106 |
107 | liveTranscoding.setUsers(users);
108 |
109 | liveTranscoding.width = PKConstants.LIVE_TRANSCODING_WIDTH * 2;
110 | liveTranscoding.height = PKConstants.LIVE_TRANSCODING_HEIGHT;
111 |
112 | liveTranscoding.videoBitrate = PKConstants.LIVE_TRANSCODING_BITRATE;
113 | liveTranscoding.videoFramerate = PKConstants.LIVE_TRANSCODING_FPS;
114 | liveTranscoding.lowLatency = true;
115 | break;
116 |
117 | default:
118 | break;
119 | }
120 | return liveTranscoding;
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk;
2 |
3 | import android.Manifest;
4 | import android.annotation.TargetApi;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.os.Build;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import android.os.Bundle;
11 | import android.support.v4.content.ContextCompat;
12 | import android.support.v4.content.PermissionChecker;
13 | import android.support.v7.app.AppCompatActivity;
14 | import android.text.TextUtils;
15 | import android.view.View;
16 | import android.widget.EditText;
17 | import android.widget.Toast;
18 |
19 | import io.agora.pk.utils.PKConstants;
20 | import io.agora.rtc.Constants;
21 |
22 | public class MainActivity extends AppCompatActivity {
23 |
24 | private EditText mEtChannel;
25 |
26 | @Override
27 | protected void onCreate(@Nullable Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(R.layout.activity_main);
30 |
31 | mEtChannel = findViewById(R.id.et_channel);
32 | }
33 |
34 | public void onBroadcastClicked(View v) {
35 | String channel = mEtChannel.getText().toString();
36 | if (TextUtils.isEmpty(channel)) {
37 | Toast.makeText(this, R.string.main_channel_hint, Toast.LENGTH_LONG).show();
38 | return;
39 | }
40 |
41 | ((PKApplication) getApplication()).getPkConfig().setBroadcasterAccount(channel);
42 |
43 | if (checkSelfPermissions()) {
44 | forwardTo(Constants.CLIENT_ROLE_BROADCASTER);
45 | }
46 | }
47 |
48 | private void forwardTo(int clientRole) {
49 | Intent intent = new Intent(MainActivity.this, PKBroadcasterActivity.class);
50 | intent.putExtra(PKConstants.USER_CLIENT_ROLE, clientRole);
51 | startActivity(intent);
52 | }
53 |
54 | private static final int PERMISSION_REQ_ID = 1024;
55 |
56 | @TargetApi(Build.VERSION_CODES.M)
57 | private void askPermission() {
58 | requestPermissions(new String[]{
59 | Manifest.permission.CAMERA,
60 | Manifest.permission.RECORD_AUDIO,
61 | Manifest.permission.WRITE_EXTERNAL_STORAGE},
62 | PERMISSION_REQ_ID);
63 | }
64 |
65 | private boolean checkSelfPermissions() {
66 | return checkSelfPermission(Manifest.permission.RECORD_AUDIO, PERMISSION_REQ_ID) &&
67 | checkSelfPermission(Manifest.permission.CAMERA, PERMISSION_REQ_ID) &&
68 | checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSION_REQ_ID);
69 | }
70 |
71 | @Override
72 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
73 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
74 | if (requestCode == PERMISSION_REQ_ID) {
75 | for (int g : grantResults) {
76 | if (g != PermissionChecker.PERMISSION_GRANTED) {
77 | return;
78 | }
79 | }
80 | }
81 | }
82 |
83 | public boolean checkSelfPermission(String permission, int requestCode) {
84 | if (ContextCompat.checkSelfPermission(this,
85 | permission)
86 | != PackageManager.PERMISSION_GRANTED) {
87 |
88 | askPermission();
89 | return false;
90 | }
91 | return true;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/PKApplication.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk;
2 |
3 | import android.app.Application;
4 |
5 | import java.lang.ref.WeakReference;
6 |
7 | import io.agora.pk.model.WorkerThread;
8 |
9 | public class PKApplication extends Application {
10 | private WorkerThread mWorkerThread;
11 | private PKConfig pkConfig;
12 |
13 | @Override
14 | public void onCreate() {
15 | super.onCreate();
16 | if (pkConfig == null) {
17 | pkConfig = new PKConfig();
18 | }
19 | }
20 |
21 | public synchronized void initWorkerThread(){
22 | if (mWorkerThread == null) {
23 | mWorkerThread = new WorkerThread(new WeakReference<>(getApplicationContext()));
24 | mWorkerThread.start();
25 | mWorkerThread.waitForReady();
26 | }
27 | }
28 |
29 | public synchronized WorkerThread getWorkerThread() {
30 | return mWorkerThread;
31 | }
32 |
33 | public synchronized PKConfig getPkConfig() {
34 | return pkConfig;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/PKBroadcasterActivity.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.graphics.Color;
7 | import android.graphics.drawable.ColorDrawable;
8 | import android.os.Bundle;
9 | import android.support.v7.app.AlertDialog;
10 | import android.util.Log;
11 | import android.view.LayoutInflater;
12 | import android.view.SurfaceView;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.widget.Button;
16 | import android.widget.EditText;
17 | import android.widget.FrameLayout;
18 | import android.widget.TextView;
19 | import android.widget.Toast;
20 |
21 | import java.util.HashMap;
22 |
23 | import io.agora.pk.model.AGEventHandler;
24 | import io.agora.pk.utils.PKConstants;
25 | import io.agora.pk.utils.StringUtils;
26 | import io.agora.rtc.Constants;
27 | import io.agora.rtc.IRtcEngineEventHandler;
28 | import io.agora.rtc.RtcEngine;
29 | import io.agora.rtc.video.VideoCanvas;
30 |
31 | public class PKBroadcasterActivity extends BaseActivity implements AGEventHandler {
32 |
33 | private static final String TAG = "PKBroadcaster";
34 |
35 | private int mClientRole;
36 |
37 | private FrameLayout mFLSingleView;
38 |
39 | private FrameLayout mFLPKViewLeft;
40 | private FrameLayout mFLPKViewRight;
41 |
42 | private FrameLayout mFLPKMidBoard;
43 |
44 | private Button mBtnExitPk;
45 |
46 | private boolean isPKnow = false;
47 | private boolean isBroadcaster = false;
48 |
49 | private int mLocalUid = 0;
50 | private int mRemoteUid = 0;
51 | private HashMap mUserList = new HashMap<>();
52 |
53 | private SurfaceView localView;
54 | private SurfaceView remoteView;
55 |
56 | private TextView mTvStartPk;
57 |
58 | private Button mBtnVCopyRtmpPullUrl;
59 | private TextView mTvRtmpPullUrl;
60 |
61 | @Override
62 | protected void onCreate(Bundle savedInstanceState) {
63 | super.onCreate(savedInstanceState);
64 | setContentView(R.layout.activity_pk_broadcaster);
65 |
66 | mClientRole = getIntent().getIntExtra(PKConstants.USER_CLIENT_ROLE, Constants.CLIENT_ROLE_AUDIENCE);
67 | }
68 |
69 | @Override
70 | protected void initUIandEvent() {
71 | mFLSingleView = findViewById(R.id.fl_chat_room_main_video_view);
72 | mFLPKViewLeft = findViewById(R.id.fl_chat_room_main_pk_board_left);
73 | mFLPKViewRight = findViewById(R.id.fl_chat_room_main_pk_board_right);
74 | mFLPKMidBoard = findViewById(R.id.fl_chat_room_main_pk_board);
75 | mTvStartPk = findViewById(R.id.et_chat_room_main_start_pk);
76 | mBtnExitPk = findViewById(R.id.btn_main_pk_exit_pk);
77 |
78 | mBtnVCopyRtmpPullUrl = findViewById(R.id.btn_copy_rtmp_pull_url);
79 | mBtnVCopyRtmpPullUrl.setOnClickListener(new View.OnClickListener() {
80 |
81 | @Override
82 | public void onClick(View view) {
83 | copyRtmpPullUrl();
84 | }
85 | });
86 | mTvRtmpPullUrl = findViewById(R.id.tv_rtmp_pull_url);
87 |
88 | initEngine();
89 | }
90 |
91 | public void initEngine() {
92 | event().addEventHandler(this);
93 |
94 | workThread().configEngine(mClientRole);
95 | if (mClientRole == Constants.CLIENT_ROLE_BROADCASTER) {
96 | isBroadcaster = true;
97 | workThread().joinChannel(((PKApplication) getApplication()).getPkConfig().getBroadcasterAccount(), 0);
98 | } else if (mClientRole == Constants.CLIENT_ROLE_AUDIENCE) {
99 | isBroadcaster = false;
100 | }
101 | changeViewToSingle();
102 | localView = RtcEngine.CreateRendererView(this);
103 | remoteView = RtcEngine.CreateRendererView(this);
104 | }
105 |
106 | @Override
107 | protected void deInitUIandEvent() {
108 |
109 | }
110 |
111 | public void onBackClicked(View v) {
112 | if (isBroadcaster) {
113 | removePublishUrl();
114 | workThread().leaveChannel();
115 | }
116 |
117 | mUserList.clear();
118 | finish();
119 | }
120 |
121 | @Override
122 | public void onBackPressed() {
123 | super.onBackPressed();
124 | onBackClicked(null);
125 | }
126 |
127 | // exit pk
128 | public void onExitPKClicked(View v) {
129 | isPKnow = false;
130 |
131 | if (remoteView.getParent() != null)
132 | ((ViewGroup) (remoteView.getParent())).removeAllViews();
133 |
134 | ((PKApplication) getApplication()).getPkConfig().setPkMediaAccount("");
135 |
136 | mUserList.clear();
137 | removePublishUrl();
138 | workThread().leaveChannel();
139 | workThread().joinChannel(((PKApplication) getApplication()).getPkConfig().getBroadcasterAccount(), 0);
140 | changeViewToSingle();
141 | }
142 |
143 | // start pk, input a room channel to start pk
144 | public void onStartPKClicked(View v) {
145 | final AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
146 | View rootView = LayoutInflater.from(this).inflate(R.layout.pop_view_pk, null);
147 | alertDialog.setView(rootView);
148 | final AlertDialog dialog = alertDialog.create();
149 | if (null != dialog.getWindow())
150 | dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
151 | dialog.show();
152 |
153 | Button btn = rootView.findViewById(R.id.btn_start_pk);
154 | final EditText et = rootView.findViewById(R.id.et_pk_channel);
155 | btn.setOnClickListener(new View.OnClickListener() {
156 | @Override
157 | public void onClick(View view) {
158 | if (!StringUtils.validate(et.getText().toString())) {
159 | Toast.makeText(PKBroadcasterActivity.this, "please input a channel account", Toast.LENGTH_SHORT).show();
160 | return;
161 | }
162 |
163 | isPKnow = true;
164 | ((PKApplication) getApplication()).getPkConfig().setPkMediaAccount(et.getText().toString());
165 | removePublishUrl();
166 | workThread().leaveChannel();
167 | mUserList.clear();
168 | workThread().joinChannel(((PKApplication) getApplication()).getPkConfig().getPkMediaAccount(), 0);
169 | dialog.dismiss();
170 | }
171 | });
172 | }
173 |
174 | public void changeViewToSingle() {
175 | mFLPKMidBoard.setVisibility(View.INVISIBLE);
176 | mFLSingleView.setVisibility(View.VISIBLE);
177 |
178 | mFLSingleView.setBackgroundColor(Color.BLACK);
179 | if (isBroadcaster)
180 | mTvStartPk.setVisibility(View.VISIBLE);
181 | else {
182 | mTvStartPk.setVisibility(View.INVISIBLE);
183 | }
184 | }
185 |
186 | public void changeViewToPkBroadcaster() {
187 | mFLSingleView.setVisibility(View.INVISIBLE);
188 | mFLPKMidBoard.setVisibility(View.VISIBLE);
189 | mTvStartPk.setVisibility(View.VISIBLE);
190 |
191 | mFLPKViewRight.setVisibility(View.VISIBLE);
192 | mFLPKViewLeft.setVisibility(View.VISIBLE);
193 | mBtnExitPk.setVisibility(View.VISIBLE);
194 | }
195 |
196 | public void setLocalPreviewView(int uid) {
197 | workThread().preview(true, localView, uid);
198 |
199 | if (mFLSingleView.getChildCount() > 0) {
200 | mFLSingleView.removeAllViews();
201 | }
202 |
203 | if (localView.getParent() != null)
204 | ((ViewGroup) (localView.getParent())).removeAllViews();
205 |
206 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
207 | localView.setZOrderOnTop(false);
208 | localView.setZOrderMediaOverlay(false);
209 | localView.setLayoutParams(lp);
210 | mFLSingleView.addView(localView);
211 | }
212 |
213 | public void setLocalPkLeftView(int uid) {
214 | workThread().preview(true, localView, uid);
215 |
216 | if (mFLPKViewLeft.getChildCount() > 0)
217 | mFLPKViewLeft.removeAllViews();
218 |
219 | if (localView.getParent() != null)
220 | ((ViewGroup) (localView.getParent())).removeAllViews();
221 |
222 | mFLPKViewLeft.addView(localView);
223 | }
224 |
225 | public void setRemotePkRightView(int uid) {
226 | if (mFLPKViewRight.getChildCount() > 0)
227 | mFLPKViewRight.removeAllViews();
228 |
229 | if (remoteView.getParent() != null)
230 | ((ViewGroup) (remoteView.getParent())).removeAllViews();
231 |
232 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
233 | remoteView.setZOrderOnTop(false);
234 | remoteView.setZOrderMediaOverlay(false);
235 | remoteView.setLayoutParams(lp);
236 |
237 | rtcEngine().setupRemoteVideo(new VideoCanvas(remoteView, Constants.RENDER_MODE_HIDDEN, uid));
238 | mFLPKViewRight.addView(remoteView);
239 | }
240 |
241 | @Override
242 | public void onJoinChannelSuccess(final String channel, final int uid, int elapsed) {
243 | runOnUiThread(new Runnable() {
244 | @Override
245 | public void run() {
246 | Log.d(TAG, "onJoinChannelSuccess channel = " + channel + " uid = " + (uid & 0XFFFFFFFFL));
247 |
248 | mLocalUid = uid;
249 | mUserList.put(mLocalUid, mLocalUid);
250 | if (isPKnow) {
251 | changeViewToPkBroadcaster();
252 | setLocalPkLeftView(uid);
253 | } else {
254 | changeViewToSingle();
255 | setLocalPreviewView(uid);
256 | }
257 |
258 | // start CDN Streaming
259 | setLiveTranscoding();
260 | publishUrl();
261 | }
262 | });
263 | }
264 |
265 | @Override
266 | public void onUserJoined(final int uid, int elapsed) {
267 | runOnUiThread(new Runnable() {
268 | @Override
269 | public void run() {
270 | if (mUserList.size() < PKConstants.MAX_PK_COUNT) {
271 | mRemoteUid = uid;
272 | mUserList.put(uid, uid);
273 | setLiveTranscoding();
274 | setRemotePkRightView(uid);
275 | }
276 | }
277 | });
278 |
279 | }
280 |
281 | @Override
282 | public void onStreamPublished(String url, int error) {
283 | }
284 |
285 | @Override
286 | public void onStreamUnpublished(String url) {
287 | }
288 |
289 | @Override
290 | public void onError(int err) {
291 | }
292 |
293 | @Override
294 | public void onUserOffline(final int uid, int reason) {
295 | runOnUiThread(new Runnable() {
296 | @Override
297 | public void run() {
298 | if (mUserList.keySet().contains(uid)) {
299 | mUserList.remove(uid);
300 | onExitPKClicked(null);
301 | setLiveTranscoding();
302 | }
303 | }
304 | });
305 |
306 | }
307 |
308 | @Override
309 | public void onLeaveChannel(IRtcEngineEventHandler.RtcStats stats) {
310 | }
311 |
312 | private String rtmpPullUrl() {
313 | return PKConstants.PUBLISH_PULL_URL + ((PKApplication) getApplication()).getPkConfig().getBroadcasterAccount();
314 | }
315 |
316 | private void copyRtmpPullUrl() {
317 | ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
318 | ClipData mClipData = ClipData.newPlainText("Label", rtmpPullUrl());
319 | cm.setPrimaryClip(mClipData);
320 |
321 | Toast.makeText(this, R.string.already_copied, Toast.LENGTH_LONG).show();
322 | }
323 |
324 | public void publishUrl() {
325 | rtcEngine().addPublishStreamUrl(PKConstants.PUBLISH_URL + ((PKApplication) getApplication()).getPkConfig().getBroadcasterAccount(), true);
326 |
327 | mTvRtmpPullUrl.setText(rtmpPullUrl());
328 | }
329 |
330 | public void removePublishUrl() {
331 | rtcEngine().removePublishStreamUrl(PKConstants.PUBLISH_URL + ((PKApplication) getApplication()).getPkConfig().getBroadcasterAccount());
332 | }
333 |
334 | public void setLiveTranscoding() {
335 | rtcEngine().setLiveTranscoding(updateLiveTranscoding(mLocalUid, mRemoteUid, mUserList));
336 | }
337 |
338 | }
339 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/PKConfig.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk;
2 |
3 | public class PKConfig {
4 | // all of the audience join the broadcaster signal channel
5 | // if the broadcaster want to pk, he should join another media channel, publish with old channel account
6 | // and keep the signal channel
7 | private String broadcasterAccount;
8 | // just for broadcaster pk to join media channel
9 | private String pkMediaAccount;
10 |
11 | public String getBroadcasterAccount() {
12 | return broadcasterAccount;
13 | }
14 |
15 | public void setBroadcasterAccount(String broadcasterAccount) {
16 | this.broadcasterAccount = broadcasterAccount;
17 | }
18 |
19 | public String getPkMediaAccount() {
20 | return pkMediaAccount;
21 | }
22 |
23 | public void setPkMediaAccount(String pkMediaAccount) {
24 | this.pkMediaAccount = pkMediaAccount;
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/model/AGEventHandler.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk.model;
2 |
3 | import io.agora.rtc.IRtcEngineEventHandler;
4 |
5 | public interface AGEventHandler {
6 | void onJoinChannelSuccess(String channel, int uid, int elapsed);
7 |
8 | void onUserJoined(int uid, int elapsed);
9 |
10 | void onStreamPublished(String url, int error);
11 |
12 | void onStreamUnpublished(String url);
13 |
14 | void onError(int err);
15 |
16 | void onUserOffline(int uid, int reason);
17 |
18 | void onLeaveChannel(IRtcEngineEventHandler.RtcStats stats);
19 | }
20 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/model/MyEngineEventHandler.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk.model;
2 |
3 | import android.util.Log;
4 |
5 | import java.util.Iterator;
6 | import java.util.concurrent.ConcurrentHashMap;
7 |
8 | import io.agora.rtc.IRtcEngineEventHandler;
9 |
10 | public class MyEngineEventHandler {
11 |
12 | private ConcurrentHashMap handlers = new ConcurrentHashMap<>();
13 |
14 | public void addEventHandler(AGEventHandler handler) {
15 | handlers.put(0, handler);
16 | }
17 |
18 | public void removeEventHandler(AGEventHandler handler) {
19 | handlers.remove(0);
20 | }
21 |
22 | private static final String LOG_TAG = "AG_EVT";
23 |
24 | final IRtcEngineEventHandler mEventHandlerList = new IRtcEngineEventHandler() {
25 | @Override
26 | public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
27 | Log.e(LOG_TAG, "success");
28 | super.onJoinChannelSuccess(channel, uid, elapsed);
29 |
30 | if (handlers.isEmpty()) {
31 | return;
32 | }
33 |
34 | Iterator it = handlers.values().iterator();
35 | while (it.hasNext()) {
36 | it.next().onJoinChannelSuccess(channel, uid, elapsed);
37 | }
38 | }
39 |
40 | @Override
41 | public void onRejoinChannelSuccess(String channel, int uid, int elapsed) {
42 | super.onRejoinChannelSuccess(channel, uid, elapsed);
43 |
44 | if (handlers.isEmpty()) {
45 | return;
46 | }
47 |
48 | Iterator it = handlers.values().iterator();
49 | while (it.hasNext()) {
50 | it.next().onJoinChannelSuccess(channel, uid, elapsed);
51 | }
52 | }
53 |
54 | @Override
55 | public void onUserJoined(int uid, int elapsed) {
56 | Log.e(LOG_TAG, "joined");
57 | super.onUserJoined(uid, elapsed);
58 |
59 | if (handlers.isEmpty()) {
60 | return;
61 | }
62 |
63 | Iterator it = handlers.values().iterator();
64 | while (it.hasNext()) {
65 | it.next().onUserJoined(uid, elapsed);
66 | }
67 | }
68 |
69 | @Override
70 | public void onStreamPublished(String url, int error) {
71 | Log.e(LOG_TAG, "onStreamUnpublished: " + url);
72 | super.onStreamPublished(url, error);
73 |
74 | if (handlers.isEmpty()) {
75 | return;
76 | }
77 |
78 | Iterator it = handlers.values().iterator();
79 | while (it.hasNext()) {
80 | it.next().onStreamPublished(url, error);
81 | }
82 | }
83 |
84 | @Override
85 | public void onStreamUnpublished(String url) {
86 | Log.e(LOG_TAG, "onStreamUnpublished: " + url);
87 | super.onStreamUnpublished(url);
88 |
89 | if (handlers.isEmpty()) {
90 | return;
91 | }
92 |
93 | Iterator it = handlers.values().iterator();
94 | while (it.hasNext()) {
95 | it.next().onStreamUnpublished(url);
96 | }
97 | }
98 |
99 | @Override
100 | public void onError(int err) {
101 | super.onError(err);
102 | Log.e(LOG_TAG, "error: " + err);
103 |
104 | if (handlers.isEmpty()) {
105 | return;
106 | }
107 |
108 | Iterator it = handlers.values().iterator();
109 | while (it.hasNext()) {
110 | it.next().onError(err);
111 | }
112 | }
113 |
114 | @Override
115 | public void onUserOffline(int uid, int reason) {
116 | Log.d(LOG_TAG, "joined");
117 | super.onUserOffline(uid, reason);
118 |
119 | if (handlers.isEmpty()) {
120 | return;
121 | }
122 |
123 | Iterator it = handlers.values().iterator();
124 | while (it.hasNext()) {
125 | it.next().onUserOffline(uid, reason);
126 | }
127 | }
128 |
129 | @Override
130 | public void onLeaveChannel(RtcStats stats) {
131 | super.onLeaveChannel(stats);
132 |
133 | if (handlers.isEmpty()) {
134 | return;
135 | }
136 |
137 | Iterator it = handlers.values().iterator();
138 | while (it.hasNext()) {
139 | it.next().onLeaveChannel(stats);
140 | }
141 | }
142 | };
143 | }
144 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/model/WorkerThread.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk.model;
2 |
3 | import android.content.Context;
4 | import android.os.Handler;
5 | import android.os.Looper;
6 | import android.os.Message;
7 | import android.util.Log;
8 | import android.view.SurfaceView;
9 |
10 | import java.lang.ref.WeakReference;
11 |
12 | import io.agora.pk.R;
13 | import io.agora.pk.utils.PKConstants;
14 | import io.agora.pk.utils.StringUtils;
15 | import io.agora.rtc.Constants;
16 | import io.agora.rtc.RtcEngine;
17 | import io.agora.rtc.video.VideoCanvas;
18 |
19 | public class WorkerThread extends Thread {
20 | private final static String TAG = WorkerThread.class.getName();
21 |
22 | private final Context mContext;
23 |
24 | private static final int ACTION_WORKER_THREAD_QUIT = 0X1010; // quit this thread
25 |
26 | private static final int ACTION_WORKER_JOIN_CHANNEL = 0X2010;
27 |
28 | private static final int ACTION_WORKER_LEAVE_CHANNEL = 0X2011;
29 |
30 | private static final int ACTION_WORKER_CONFIG_ENGINE = 0X2012;
31 |
32 | private static final int ACTION_WORKER_SETUP_REMOTE_VIEW = 0X2013;
33 |
34 | private static final int ACTION_WORKER_PREVIEW = 0X2014;
35 |
36 | private RtcEngine mRtcEngine;
37 | private WorkerThreadHandler mWorkerHandler;
38 | private MyEngineEventHandler mMyEngineEventHandler;
39 |
40 | private boolean mReady = false;
41 |
42 | private static final class WorkerThreadHandler extends Handler {
43 | private WorkerThread mWorkerThread;
44 |
45 | public WorkerThreadHandler(WorkerThread thread) {
46 | this.mWorkerThread = thread;
47 | }
48 |
49 | public void release() {
50 | mWorkerThread = null;
51 | }
52 |
53 | @Override
54 | public void handleMessage(Message msg) {
55 | super.handleMessage(msg);
56 |
57 | switch (msg.what) {
58 | case ACTION_WORKER_JOIN_CHANNEL:
59 | mWorkerThread.joinChannel((String) msg.obj, msg.arg1);
60 | break;
61 | case ACTION_WORKER_PREVIEW:
62 | Object[] previewData = (Object[]) msg.obj;
63 | mWorkerThread.preview((boolean) previewData[0], (SurfaceView) previewData[1], (int) previewData[2]);
64 | break;
65 | case ACTION_WORKER_THREAD_QUIT:
66 | mWorkerThread.exit();
67 | break;
68 | case ACTION_WORKER_LEAVE_CHANNEL:
69 | mWorkerThread.leaveChannel();
70 | break;
71 | case ACTION_WORKER_CONFIG_ENGINE:
72 | mWorkerThread.configEngine((Integer) msg.obj);
73 | break;
74 | case ACTION_WORKER_SETUP_REMOTE_VIEW:
75 | Object[] remoteData = (Object[]) msg.obj;
76 | mWorkerThread.setupRemoteView((SurfaceView) remoteData[0], (int) remoteData[1]);
77 | break;
78 | default:
79 | throw new RuntimeException("unknown handler event");
80 | }
81 | }
82 | }
83 |
84 | public WorkerThread(WeakReference ctx) {
85 | this.mContext = ctx.get();
86 | this.mMyEngineEventHandler = new MyEngineEventHandler();
87 | }
88 |
89 | public void waitForReady() {
90 | while (!mReady) {
91 | try {
92 | Thread.sleep(20);
93 | } catch (InterruptedException e) {
94 | e.printStackTrace();
95 | }
96 | }
97 | }
98 |
99 | @Override
100 | public void run() {
101 | super.run();
102 | Looper.prepare();
103 |
104 | mWorkerHandler = new WorkerThreadHandler(this);
105 |
106 | ensureRtcEngineReadyLock();
107 |
108 | mReady = true;
109 |
110 | Looper.loop();
111 | }
112 |
113 | private void ensureRtcEngineReadyLock() {
114 | if (mRtcEngine != null)
115 | return;
116 |
117 | if (!StringUtils.validate(mContext.getString(R.string.agora_app_id)))
118 | throw new RuntimeException("You need to provide a valid Agora App Id");
119 |
120 | try {
121 | mRtcEngine = RtcEngine.create(mContext, mContext.getString(R.string.agora_app_id), mMyEngineEventHandler.mEventHandlerList);
122 | } catch (Exception e) {
123 | e.printStackTrace();
124 | }
125 |
126 | mRtcEngine.setChannelProfile(Constants.CHANNEL_PROFILE_LIVE_BROADCASTING);
127 | }
128 |
129 | public RtcEngine rtcEngine() {
130 | return mRtcEngine;
131 | }
132 |
133 | public MyEngineEventHandler eventHandler() {
134 | return mMyEngineEventHandler;
135 | }
136 |
137 | /**
138 | * call this method to exit
139 | * should ONLY call this method when this thread is running
140 | */
141 | public final void exit() {
142 | if (Thread.currentThread() != this) {
143 | Log.w(TAG, "exit() - exit app thread asynchronously");
144 | mWorkerHandler.sendEmptyMessage(ACTION_WORKER_THREAD_QUIT);
145 | return;
146 | }
147 |
148 | mReady = false;
149 |
150 | // TODO should remove all pending(read) messages
151 |
152 | Log.d(TAG, "exit() > start");
153 |
154 | // exit thread looper
155 | Looper.myLooper().quit();
156 |
157 | mWorkerHandler.release();
158 |
159 | Log.d(TAG, "exit() > end");
160 | }
161 |
162 | public final void joinChannel(final String channel, int uid) {
163 | if (Thread.currentThread() != this) {
164 | Message envelop = new Message();
165 | envelop.what = ACTION_WORKER_JOIN_CHANNEL;
166 | envelop.obj = channel;
167 | envelop.arg1 = uid;
168 | mWorkerHandler.sendMessage(envelop);
169 | return;
170 | }
171 |
172 | ensureRtcEngineReadyLock();
173 |
174 | int ret = mRtcEngine.joinChannel(null, channel, "", uid);
175 | Log.d(TAG, "joinChannel: " + ret);
176 | }
177 |
178 | public final void configEngine(int channelProfile) {
179 | if (Thread.currentThread() != this) {
180 | Message msg = Message.obtain();
181 | msg.what = ACTION_WORKER_CONFIG_ENGINE;
182 | msg.obj = channelProfile;
183 | mWorkerHandler.sendMessage(msg);
184 | return;
185 | }
186 |
187 | ensureRtcEngineReadyLock();
188 |
189 | mRtcEngine.setClientRole(channelProfile);
190 |
191 | mRtcEngine.setVideoEncoderConfiguration(PKConstants.VIDEO_CONFIGURATION);
192 |
193 | mRtcEngine.enableVideo();
194 | mRtcEngine.enableDualStreamMode(true);
195 | }
196 |
197 | public final void preview(boolean start, SurfaceView view, int uid) {
198 | if (Thread.currentThread() != this) {
199 | Message envelop = new Message();
200 | envelop.what = ACTION_WORKER_PREVIEW;
201 | envelop.obj = new Object[]{start, view, uid};
202 | mWorkerHandler.sendMessage(envelop);
203 | return;
204 | }
205 |
206 | ensureRtcEngineReadyLock();
207 |
208 | if (start) {
209 | mRtcEngine.setupLocalVideo(new VideoCanvas(view, VideoCanvas.RENDER_MODE_HIDDEN, uid));
210 | mRtcEngine.startPreview();
211 | } else {
212 | mRtcEngine.stopPreview();
213 | }
214 | }
215 |
216 | public final void setupRemoteView(SurfaceView view, int uid) {
217 | if (Thread.currentThread() != this) {
218 | Message envelop = new Message();
219 | envelop.what = ACTION_WORKER_SETUP_REMOTE_VIEW;
220 | envelop.obj = new Object[]{view, uid};
221 | mWorkerHandler.sendMessage(envelop);
222 | return;
223 | }
224 |
225 | ensureRtcEngineReadyLock();
226 |
227 | mRtcEngine.setupRemoteVideo(new VideoCanvas(view, VideoCanvas.RENDER_MODE_HIDDEN, uid));
228 | }
229 |
230 | public final void leaveChannel() {
231 | if (Thread.currentThread() != this) {
232 | Message envelop = new Message();
233 | envelop.what = ACTION_WORKER_LEAVE_CHANNEL;
234 | mWorkerHandler.sendMessage(envelop);
235 | return;
236 | }
237 | if (mRtcEngine != null) {
238 | mRtcEngine.leaveChannel();
239 | }
240 | }
241 |
242 | }
243 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/ui/CircleImageView.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk.ui;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.PaintFlagsDrawFilter;
8 | import android.graphics.Path;
9 | import android.graphics.Region;
10 | import android.os.Build;
11 | import android.support.v7.widget.AppCompatImageView;
12 | import android.util.AttributeSet;
13 |
14 | public class CircleImageView extends AppCompatImageView {
15 | private Path path;
16 | public PaintFlagsDrawFilter mPaintFlagsDrawFilter;
17 | private Paint paint;
18 |
19 | public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
20 | super(context, attrs, defStyle);
21 | init();
22 | }
23 |
24 | public CircleImageView(Context context, AttributeSet attrs) {
25 | super(context, attrs);
26 | init();
27 | }
28 |
29 | public CircleImageView(Context context) {
30 | super(context);
31 | init();
32 | }
33 |
34 | public void init() {
35 | mPaintFlagsDrawFilter = new PaintFlagsDrawFilter(0,
36 | Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
37 | paint = new Paint();
38 | paint.setAntiAlias(true);
39 | paint.setFilterBitmap(true);
40 | paint.setColor(Color.BLACK);
41 | paint.setStrokeWidth(1);
42 | }
43 |
44 | @Override
45 | protected void onDraw(Canvas cns) {
46 | float h = getMeasuredHeight() - 3.0f;
47 | float w = getMeasuredWidth() - 3.0f;
48 | if (path == null) {
49 | path = new Path();
50 | path.addCircle(
51 | w / 2.0f
52 | , h / 2.0f
53 | , (float) Math.min(w / 2.0f, (h / 2.0))
54 | , Path.Direction.CCW);
55 | path.close();
56 | }
57 | cns.drawCircle(w / 2.0f, h / 2.0f, Math.min(w / 2.0f, h / 2.0f) + 1.5f, paint);
58 | cns.save();
59 | cns.setDrawFilter(mPaintFlagsDrawFilter);
60 |
61 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
62 | cns.clipPath(path);
63 | } else {
64 | cns.clipPath(path, Region.Op.REPLACE);
65 | }
66 |
67 | cns.setDrawFilter(mPaintFlagsDrawFilter);
68 | cns.drawColor(Color.WHITE);
69 | super.onDraw(cns);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/utils/KeyboardStatusDetector.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk.utils;
2 |
3 | import android.app.Activity;
4 | import android.graphics.Rect;
5 | import android.view.View;
6 | import android.view.ViewTreeObserver;
7 |
8 | import java.lang.ref.WeakReference;
9 |
10 | public class KeyboardStatusDetector {
11 | private static final int SOFT_KEY_BOARD_MIN_HEIGH = 100;
12 |
13 | private KeyboardListener klistener;
14 | private boolean isVisible = false;
15 |
16 | public void setCallback(KeyboardListener kl) {
17 | this.klistener = kl;
18 | }
19 |
20 | public void registerActivity(WeakReference activity) {
21 | final View v = activity.get().getWindow().getDecorView().findViewById(android.R.id.content);
22 | v.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
23 | @Override
24 | public void onGlobalLayout() {
25 | Rect r = new Rect();
26 | v.getWindowVisibleDisplayFrame(r);
27 | int height = v.getRootView().getHeight() - (r.bottom - r.top);
28 |
29 | if (height > SOFT_KEY_BOARD_MIN_HEIGH) {
30 | if (!isVisible) {
31 | isVisible = true;
32 | if (klistener != null)
33 | klistener.onKeyBoardStatusChanged(true, height);
34 | } else {
35 | isVisible = false;
36 | if (klistener != null)
37 | klistener.onKeyBoardStatusChanged(true, 0);
38 | }
39 | }
40 | }
41 | });
42 | }
43 |
44 | public interface KeyboardListener {
45 | void onKeyBoardStatusChanged(boolean v, int height);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/utils/MessageUtils.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk.utils;
2 |
3 | import android.text.TextUtils;
4 |
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 |
8 | import java.util.concurrent.ConcurrentHashMap;
9 |
10 | public class MessageUtils {
11 |
12 | private static ConcurrentHashMap maps = new ConcurrentHashMap<>();
13 | private static ConcurrentHashMap maps2 = new ConcurrentHashMap<>();
14 |
15 | public static String switchToChatJsonMsg(String msg) {
16 | maps.clear();
17 | maps.put("type", "chat");
18 | maps.put("data", msg);
19 | return new JSONObject(maps).toString();
20 | }
21 |
22 | public static String switchToCtrlMsg(boolean msg) {
23 | maps2.clear();
24 | maps2.put("type", "pkStatus");
25 | maps2.put("data", msg);
26 | return new JSONObject(maps2).toString();
27 | }
28 |
29 | public static Object getMessage(String content) {
30 | try {
31 | JSONObject obj = new JSONObject(content);
32 | String s = obj.optString("type", "");
33 | if (TextUtils.isEmpty(s))
34 | return null;
35 |
36 | if ("chat".equals(s)) {
37 | return obj.optString("data", "");
38 | } else if ("pkStatus".equals(s)) {
39 | return Boolean.parseBoolean(obj.optString("data"));
40 | }
41 |
42 | } catch (JSONException e) {
43 | throw new RuntimeException("json error!");
44 | }
45 |
46 | return null;
47 | }
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/utils/PKConstants.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk.utils;
2 |
3 | import io.agora.rtc.video.VideoEncoderConfiguration;
4 |
5 | public class PKConstants {
6 |
7 | public final static String BUNDLE_ACCOUNT_NAME = "ACCOUNT_NAME";
8 | public final static String USER_CLIENT_ROLE = "USER_CLIENT_ROLE";
9 | public final static VideoEncoderConfiguration VIDEO_CONFIGURATION = new VideoEncoderConfiguration(VideoEncoderConfiguration.VD_640x480,
10 | VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15, VideoEncoderConfiguration.STANDARD_BITRATE,
11 | VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT);
12 |
13 | public final static int MAX_PK_COUNT = 2;
14 |
15 | public final static int LIVE_TRANSCODING_WIDTH = 360;
16 | public final static int LIVE_TRANSCODING_HEIGHT = 640;
17 | public final static int LIVE_TRANSCODING_FPS = 15;
18 | public final static int LIVE_TRANSCODING_BITRATE = 1200;
19 |
20 | public final static String PUBLISH_URL = <##YOUR PUBLISH RTMP URL##>;
21 | public final static String PUBLISH_PULL_URL = <##YOUR PLAY RTMP URL##>;
22 | }
23 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/java/io/agora/pk/utils/StringUtils.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk.utils;
2 |
3 | import android.text.TextUtils;
4 |
5 | public class StringUtils {
6 | private final static String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
7 |
8 | public static String random(int len) {
9 | StringBuilder sb = new StringBuilder();
10 | for (int i = 0; i < len; i++) {
11 | sb.append(chars.charAt((int) (Math.random() * (len + 1))));
12 | }
13 |
14 | return sb.toString();
15 | }
16 |
17 | public static boolean validate(String s) {
18 | return null != s && !TextUtils.isEmpty(s);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/jniLibs/arm64-v8a/PLACEHOLDER:
--------------------------------------------------------------------------------
1 | libagora-rtc-sdk-jni.so
2 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/jniLibs/armeabi-v7a/PLACEHOLDER:
--------------------------------------------------------------------------------
1 | libagora-rtc-sdk-jni.so
2 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/jniLibs/x86/PLACEHOLDER:
--------------------------------------------------------------------------------
1 | libagora-rtc-sdk-jni.so
2 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/chat_room_et_style_2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/chat_room_main_exit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/chat_room_main_exit.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/chat_room_main_like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/chat_room_main_like.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/chat_room_main_pk_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/chat_room_main_pk_support.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/chat_room_main_pk_support.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/main_btn_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/pk_exit_btn_style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_01.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_02.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_03.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_04.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_05.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_06.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_07.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/drawable/user_icon_08.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
26 |
27 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/layout/activity_pk_broadcaster.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
19 |
20 |
26 |
27 |
33 |
34 |
40 |
41 |
42 |
50 |
51 |
60 |
61 |
62 |
69 |
70 |
86 |
87 |
97 |
98 |
108 |
109 |
117 |
118 |
122 |
123 |
128 |
129 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/layout/pop_view_pk.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
21 |
22 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
8 | #3EAEFF
9 | #000000
10 | #ffffff
11 |
12 | #00000000
13 | #66000000
14 |
15 | #8FD3F5
16 | #D3FDFF
17 |
18 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 | <#YOUR APP ID#>
9 |
10 | AgoraPKOnline
11 |
12 | 请输入房间名
13 |
14 | 开始直播
15 |
16 | 退出PK
17 | 发起PK
18 |
19 | 复制
20 | 已复制
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/app/src/test/java/io/agora/pk/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package io.agora.pk;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.3.1'
11 |
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 | ext {
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/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 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-Android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jan 29 10:30:46 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-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/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 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/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 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/Agora-Online-PK-Android/tools/gradle-on-demand.gradle:
--------------------------------------------------------------------------------
1 | gradle.startParameter.taskNames.each { task ->
2 | def taskName = task.split(":").last()
3 | switch (taskName) {
4 | case "uploadArchives":
5 | apply from: new File(rootProject.projectDir, 'tools/gradle-mvn-push.gradle')
6 | break;
7 | case "bintrayUpload":
8 | apply from: new File(rootProject.projectDir, 'tools/gradle-bintray-upload.gradle')
9 | break;
10 | default:
11 | // do nothing
12 | break;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/.gitignore:
--------------------------------------------------------------------------------
1 | xcuserdata
2 | .DS_Store
3 | *.xcscmblueprint
4 | libs
5 | *.framework
6 |
7 | # Xcode
8 | #
9 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
10 |
11 | ## Build generated
12 | build/
13 | DerivedData/
14 |
15 | ## Various settings
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata/
25 |
26 | ## Other
27 | *.moved-aside
28 | *.xccheckout
29 | *.xcscmblueprint
30 |
31 | ## Obj-C/Swift specific
32 | *.hmap
33 | *.ipa
34 | *.dSYM.zip
35 | *.dSYM
36 |
37 | # CocoaPods
38 | #
39 | # We recommend against adding the Pods directory to your .gitignore. However
40 | # you should judge for yourself, the pros and cons are mentioned at:
41 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
42 | #
43 | # Pods/
44 |
45 | # Carthage
46 | #
47 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
48 | # Carthage/Checkouts
49 |
50 | Carthage/Build
51 |
52 | # fastlane
53 | #
54 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
55 | # screenshots whenever they are needed.
56 | # For more information about the recommended setup visit:
57 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
58 |
59 | fastlane/report.xml
60 | fastlane/Preview.html
61 | fastlane/screenshots
62 | fastlane/test_output
63 |
64 | # Code Injection
65 | #
66 | # After new code Injection tools there's a generated folder /iOSInjectionProject
67 | # https://github.com/johnno1962/injectionforxcode
68 |
69 | iOSInjectionProject/
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 48;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0F1866EE20C8EA7D0062CB18 /* VideoSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F1866ED20C8EA7D0062CB18 /* VideoSession.swift */; };
11 | 0F1F82D420CFD5F500A500D3 /* CommonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F1F82D320CFD5F400A500D3 /* CommonExtensions.swift */; };
12 | 0F8F038020C52F3C00E0F395 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F037F20C52F3C00E0F395 /* LoginViewController.swift */; };
13 | 0F8F038320C52F9A00E0F395 /* KeyCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F038220C52F9A00E0F395 /* KeyCenter.swift */; };
14 | 0F8F038520C5306F00E0F395 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F038420C5306F00E0F395 /* Constants.swift */; };
15 | 0F8F039020C543BF00E0F395 /* AlertUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F038F20C543BE00E0F395 /* AlertUtil.swift */; };
16 | 0F8F039820C63A6200E0F395 /* RoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F039720C63A6200E0F395 /* RoomViewController.swift */; };
17 | 0F8F039A20C63B6900E0F395 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F039920C63B6900E0F395 /* Accelerate.framework */; };
18 | 0F8F039C20C63BAE00E0F395 /* VideoToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F039B20C63BAE00E0F395 /* VideoToolbox.framework */; };
19 | 0F8F039E20C63BBD00E0F395 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F039D20C63BBD00E0F395 /* CoreVideo.framework */; };
20 | 0F8F03A020C63BC700E0F395 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F039F20C63BC600E0F395 /* AVFoundation.framework */; };
21 | 0F8F03A220C63BD700E0F395 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F03A120C63BD700E0F395 /* CoreAudio.framework */; };
22 | 0F8F03A420C63BDE00E0F395 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F03A320C63BDE00E0F395 /* AudioToolbox.framework */; };
23 | 0F8F03A620C63BEE00E0F395 /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F03A520C63BED00E0F395 /* CoreTelephony.framework */; };
24 | 0F8F03A820C63BF600E0F395 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F03A720C63BF600E0F395 /* CoreMedia.framework */; };
25 | 0F8F03AC20C63C1100E0F395 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F03AB20C63C1100E0F395 /* SystemConfiguration.framework */; };
26 | 0F8F03AE20C63C1D00E0F395 /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F8F03AD20C63C1D00E0F395 /* libresolv.tbd */; };
27 | 0F8F03B620C67ABD00E0F395 /* PopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F8F03B520C67ABD00E0F395 /* PopView.swift */; };
28 | 0F8F03B820C67E8C00E0F395 /* PopView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0F8F03B720C67E8C00E0F395 /* PopView.xib */; };
29 | 3802EF3A225CC7290065A2E1 /* CoreML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3802EF39225CC7280065A2E1 /* CoreML.framework */; };
30 | 3802EF3B225CC7A40065A2E1 /* AgoraRtcEngineKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A730EFC02101CC47003174AD /* AgoraRtcEngineKit.framework */; };
31 | A7B5417D20BF9C3F0023FE2E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7B5417C20BF9C3F0023FE2E /* AppDelegate.swift */; };
32 | A7B5418220BF9C3F0023FE2E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A7B5418020BF9C3F0023FE2E /* Main.storyboard */; };
33 | A7B5418420BF9C400023FE2E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A7B5418320BF9C400023FE2E /* Assets.xcassets */; };
34 | A7B5418720BF9C400023FE2E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A7B5418520BF9C400023FE2E /* LaunchScreen.storyboard */; };
35 | A7B541A320BF9ED10023FE2E /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = A7B541A220BF9ED10023FE2E /* libc++.tbd */; };
36 | /* End PBXBuildFile section */
37 |
38 | /* Begin PBXFileReference section */
39 | 0F1866ED20C8EA7D0062CB18 /* VideoSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoSession.swift; sourceTree = ""; };
40 | 0F1866F920CE7A9A0062CB18 /* libstdc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libstdc++.tbd"; path = "usr/lib/libstdc++.tbd"; sourceTree = SDKROOT; };
41 | 0F1866FB20CE7AAE0062CB18 /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; };
42 | 0F1866FD20CE7AB80062CB18 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
43 | 0F18673120CE86200062CB18 /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; };
44 | 0F18675920CF6C380062CB18 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
45 | 0F18675B20CF6C4E0062CB18 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
46 | 0F18675E20CF6CD90062CB18 /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; };
47 | 0F18676020CF6CE20062CB18 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
48 | 0F1F82D320CFD5F400A500D3 /* CommonExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommonExtensions.swift; sourceTree = ""; };
49 | 0F8F037F20C52F3C00E0F395 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; };
50 | 0F8F038220C52F9A00E0F395 /* KeyCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyCenter.swift; sourceTree = ""; };
51 | 0F8F038420C5306F00E0F395 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
52 | 0F8F038F20C543BE00E0F395 /* AlertUtil.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertUtil.swift; sourceTree = ""; };
53 | 0F8F039720C63A6200E0F395 /* RoomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomViewController.swift; sourceTree = ""; };
54 | 0F8F039920C63B6900E0F395 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
55 | 0F8F039B20C63BAE00E0F395 /* VideoToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VideoToolbox.framework; path = System/Library/Frameworks/VideoToolbox.framework; sourceTree = SDKROOT; };
56 | 0F8F039D20C63BBD00E0F395 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
57 | 0F8F039F20C63BC600E0F395 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
58 | 0F8F03A120C63BD700E0F395 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
59 | 0F8F03A320C63BDE00E0F395 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
60 | 0F8F03A520C63BED00E0F395 /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
61 | 0F8F03A720C63BF600E0F395 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
62 | 0F8F03A920C63BFF00E0F395 /* CoreMotion.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMotion.framework; path = System/Library/Frameworks/CoreMotion.framework; sourceTree = SDKROOT; };
63 | 0F8F03AB20C63C1100E0F395 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
64 | 0F8F03AD20C63C1D00E0F395 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
65 | 0F8F03B520C67ABD00E0F395 /* PopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopView.swift; sourceTree = ""; };
66 | 0F8F03B720C67E8C00E0F395 /* PopView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PopView.xib; sourceTree = ""; };
67 | 3802EF39225CC7280065A2E1 /* CoreML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreML.framework; path = System/Library/Frameworks/CoreML.framework; sourceTree = SDKROOT; };
68 | A730EFC02101CC47003174AD /* AgoraRtcEngineKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AgoraRtcEngineKit.framework; path = "Agora-Online-PK/AgoraRtcEngineKit.framework"; sourceTree = ""; };
69 | A7B5417920BF9C3F0023FE2E /* Agora-Online-PK.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Agora-Online-PK.app"; sourceTree = BUILT_PRODUCTS_DIR; };
70 | A7B5417C20BF9C3F0023FE2E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
71 | A7B5418120BF9C3F0023FE2E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
72 | A7B5418320BF9C400023FE2E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
73 | A7B5418620BF9C400023FE2E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
74 | A7B5418820BF9C400023FE2E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
75 | A7B541A220BF9ED10023FE2E /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; };
76 | /* End PBXFileReference section */
77 |
78 | /* Begin PBXFrameworksBuildPhase section */
79 | A7B5417620BF9C3F0023FE2E /* Frameworks */ = {
80 | isa = PBXFrameworksBuildPhase;
81 | buildActionMask = 2147483647;
82 | files = (
83 | 3802EF3A225CC7290065A2E1 /* CoreML.framework in Frameworks */,
84 | 0F8F03AE20C63C1D00E0F395 /* libresolv.tbd in Frameworks */,
85 | 0F8F03AC20C63C1100E0F395 /* SystemConfiguration.framework in Frameworks */,
86 | 0F8F03A820C63BF600E0F395 /* CoreMedia.framework in Frameworks */,
87 | 0F8F03A620C63BEE00E0F395 /* CoreTelephony.framework in Frameworks */,
88 | 0F8F03A420C63BDE00E0F395 /* AudioToolbox.framework in Frameworks */,
89 | 0F8F03A220C63BD700E0F395 /* CoreAudio.framework in Frameworks */,
90 | 0F8F03A020C63BC700E0F395 /* AVFoundation.framework in Frameworks */,
91 | 0F8F039E20C63BBD00E0F395 /* CoreVideo.framework in Frameworks */,
92 | 0F8F039C20C63BAE00E0F395 /* VideoToolbox.framework in Frameworks */,
93 | 0F8F039A20C63B6900E0F395 /* Accelerate.framework in Frameworks */,
94 | A7B541A320BF9ED10023FE2E /* libc++.tbd in Frameworks */,
95 | 3802EF3B225CC7A40065A2E1 /* AgoraRtcEngineKit.framework in Frameworks */,
96 | );
97 | runOnlyForDeploymentPostprocessing = 0;
98 | };
99 | /* End PBXFrameworksBuildPhase section */
100 |
101 | /* Begin PBXGroup section */
102 | 0F8F038120C52F4800E0F395 /* Controllers */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 0F8F037F20C52F3C00E0F395 /* LoginViewController.swift */,
106 | 0F8F039720C63A6200E0F395 /* RoomViewController.swift */,
107 | );
108 | path = Controllers;
109 | sourceTree = "";
110 | };
111 | 0F8F038620C5307500E0F395 /* Contants */ = {
112 | isa = PBXGroup;
113 | children = (
114 | 0F8F038220C52F9A00E0F395 /* KeyCenter.swift */,
115 | 0F8F038420C5306F00E0F395 /* Constants.swift */,
116 | );
117 | path = Contants;
118 | sourceTree = "";
119 | };
120 | 0F8F039120C5445C00E0F395 /* Utils */ = {
121 | isa = PBXGroup;
122 | children = (
123 | 0F1F82D320CFD5F400A500D3 /* CommonExtensions.swift */,
124 | 0F8F038F20C543BE00E0F395 /* AlertUtil.swift */,
125 | );
126 | path = Utils;
127 | sourceTree = "";
128 | };
129 | 0F8F039220C549B100E0F395 /* Views */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 0F1866ED20C8EA7D0062CB18 /* VideoSession.swift */,
133 | 0F8F03B520C67ABD00E0F395 /* PopView.swift */,
134 | 0F8F03B720C67E8C00E0F395 /* PopView.xib */,
135 | );
136 | path = Views;
137 | sourceTree = "";
138 | };
139 | A7B5417020BF9C3F0023FE2E = {
140 | isa = PBXGroup;
141 | children = (
142 | A7B5417B20BF9C3F0023FE2E /* Agora-Online-PK */,
143 | A7B5417A20BF9C3F0023FE2E /* Products */,
144 | A7B5419E20BF9EC90023FE2E /* Frameworks */,
145 | );
146 | sourceTree = "";
147 | };
148 | A7B5417A20BF9C3F0023FE2E /* Products */ = {
149 | isa = PBXGroup;
150 | children = (
151 | A7B5417920BF9C3F0023FE2E /* Agora-Online-PK.app */,
152 | );
153 | name = Products;
154 | sourceTree = "";
155 | };
156 | A7B5417B20BF9C3F0023FE2E /* Agora-Online-PK */ = {
157 | isa = PBXGroup;
158 | children = (
159 | 0F8F038120C52F4800E0F395 /* Controllers */,
160 | 0F8F039220C549B100E0F395 /* Views */,
161 | 0F8F038620C5307500E0F395 /* Contants */,
162 | 0F8F039120C5445C00E0F395 /* Utils */,
163 | A7B5417C20BF9C3F0023FE2E /* AppDelegate.swift */,
164 | A7B5418020BF9C3F0023FE2E /* Main.storyboard */,
165 | A7B5418320BF9C400023FE2E /* Assets.xcassets */,
166 | A7B5418520BF9C400023FE2E /* LaunchScreen.storyboard */,
167 | A7B5418820BF9C400023FE2E /* Info.plist */,
168 | );
169 | path = "Agora-Online-PK";
170 | sourceTree = "";
171 | };
172 | A7B5419E20BF9EC90023FE2E /* Frameworks */ = {
173 | isa = PBXGroup;
174 | children = (
175 | 3802EF39225CC7280065A2E1 /* CoreML.framework */,
176 | A730EFC02101CC47003174AD /* AgoraRtcEngineKit.framework */,
177 | 0F18676020CF6CE20062CB18 /* MobileCoreServices.framework */,
178 | 0F18675E20CF6CD90062CB18 /* MediaPlayer.framework */,
179 | 0F18675B20CF6C4E0062CB18 /* CoreGraphics.framework */,
180 | 0F18675920CF6C380062CB18 /* QuartzCore.framework */,
181 | 0F18673120CE86200062CB18 /* OpenGLES.framework */,
182 | 0F1866FD20CE7AB80062CB18 /* libz.tbd */,
183 | 0F1866FB20CE7AAE0062CB18 /* libbz2.tbd */,
184 | 0F1866F920CE7A9A0062CB18 /* libstdc++.tbd */,
185 | 0F8F03AD20C63C1D00E0F395 /* libresolv.tbd */,
186 | 0F8F03AB20C63C1100E0F395 /* SystemConfiguration.framework */,
187 | 0F8F03A920C63BFF00E0F395 /* CoreMotion.framework */,
188 | 0F8F03A720C63BF600E0F395 /* CoreMedia.framework */,
189 | 0F8F03A520C63BED00E0F395 /* CoreTelephony.framework */,
190 | 0F8F03A320C63BDE00E0F395 /* AudioToolbox.framework */,
191 | 0F8F03A120C63BD700E0F395 /* CoreAudio.framework */,
192 | 0F8F039F20C63BC600E0F395 /* AVFoundation.framework */,
193 | 0F8F039D20C63BBD00E0F395 /* CoreVideo.framework */,
194 | 0F8F039B20C63BAE00E0F395 /* VideoToolbox.framework */,
195 | 0F8F039920C63B6900E0F395 /* Accelerate.framework */,
196 | A7B541A220BF9ED10023FE2E /* libc++.tbd */,
197 | );
198 | name = Frameworks;
199 | sourceTree = "";
200 | };
201 | /* End PBXGroup section */
202 |
203 | /* Begin PBXNativeTarget section */
204 | A7B5417820BF9C3F0023FE2E /* Agora-Online-PK */ = {
205 | isa = PBXNativeTarget;
206 | buildConfigurationList = A7B5418B20BF9C400023FE2E /* Build configuration list for PBXNativeTarget "Agora-Online-PK" */;
207 | buildPhases = (
208 | A7B5417520BF9C3F0023FE2E /* Sources */,
209 | A7B5417620BF9C3F0023FE2E /* Frameworks */,
210 | A7B5417720BF9C3F0023FE2E /* Resources */,
211 | );
212 | buildRules = (
213 | );
214 | dependencies = (
215 | );
216 | name = "Agora-Online-PK";
217 | productName = "Agora-Online-PK";
218 | productReference = A7B5417920BF9C3F0023FE2E /* Agora-Online-PK.app */;
219 | productType = "com.apple.product-type.application";
220 | };
221 | /* End PBXNativeTarget section */
222 |
223 | /* Begin PBXProject section */
224 | A7B5417120BF9C3F0023FE2E /* Project object */ = {
225 | isa = PBXProject;
226 | attributes = {
227 | LastSwiftUpdateCheck = 0930;
228 | LastUpgradeCheck = 0930;
229 | ORGANIZATIONNAME = CavanSu;
230 | TargetAttributes = {
231 | A7B5417820BF9C3F0023FE2E = {
232 | CreatedOnToolsVersion = 9.3.1;
233 | LastSwiftMigration = 0930;
234 | ProvisioningStyle = Manual;
235 | };
236 | };
237 | };
238 | buildConfigurationList = A7B5417420BF9C3F0023FE2E /* Build configuration list for PBXProject "Agora-Online-PK" */;
239 | compatibilityVersion = "Xcode 8.0";
240 | developmentRegion = en;
241 | hasScannedForEncodings = 0;
242 | knownRegions = (
243 | en,
244 | Base,
245 | );
246 | mainGroup = A7B5417020BF9C3F0023FE2E;
247 | productRefGroup = A7B5417A20BF9C3F0023FE2E /* Products */;
248 | projectDirPath = "";
249 | projectRoot = "";
250 | targets = (
251 | A7B5417820BF9C3F0023FE2E /* Agora-Online-PK */,
252 | );
253 | };
254 | /* End PBXProject section */
255 |
256 | /* Begin PBXResourcesBuildPhase section */
257 | A7B5417720BF9C3F0023FE2E /* Resources */ = {
258 | isa = PBXResourcesBuildPhase;
259 | buildActionMask = 2147483647;
260 | files = (
261 | A7B5418720BF9C400023FE2E /* LaunchScreen.storyboard in Resources */,
262 | A7B5418420BF9C400023FE2E /* Assets.xcassets in Resources */,
263 | 0F8F03B820C67E8C00E0F395 /* PopView.xib in Resources */,
264 | A7B5418220BF9C3F0023FE2E /* Main.storyboard in Resources */,
265 | );
266 | runOnlyForDeploymentPostprocessing = 0;
267 | };
268 | /* End PBXResourcesBuildPhase section */
269 |
270 | /* Begin PBXSourcesBuildPhase section */
271 | A7B5417520BF9C3F0023FE2E /* Sources */ = {
272 | isa = PBXSourcesBuildPhase;
273 | buildActionMask = 2147483647;
274 | files = (
275 | 0F8F03B620C67ABD00E0F395 /* PopView.swift in Sources */,
276 | 0F8F038020C52F3C00E0F395 /* LoginViewController.swift in Sources */,
277 | 0F8F038320C52F9A00E0F395 /* KeyCenter.swift in Sources */,
278 | A7B5417D20BF9C3F0023FE2E /* AppDelegate.swift in Sources */,
279 | 0F8F039020C543BF00E0F395 /* AlertUtil.swift in Sources */,
280 | 0F8F038520C5306F00E0F395 /* Constants.swift in Sources */,
281 | 0F1866EE20C8EA7D0062CB18 /* VideoSession.swift in Sources */,
282 | 0F1F82D420CFD5F500A500D3 /* CommonExtensions.swift in Sources */,
283 | 0F8F039820C63A6200E0F395 /* RoomViewController.swift in Sources */,
284 | );
285 | runOnlyForDeploymentPostprocessing = 0;
286 | };
287 | /* End PBXSourcesBuildPhase section */
288 |
289 | /* Begin PBXVariantGroup section */
290 | A7B5418020BF9C3F0023FE2E /* Main.storyboard */ = {
291 | isa = PBXVariantGroup;
292 | children = (
293 | A7B5418120BF9C3F0023FE2E /* Base */,
294 | );
295 | name = Main.storyboard;
296 | sourceTree = "";
297 | };
298 | A7B5418520BF9C400023FE2E /* LaunchScreen.storyboard */ = {
299 | isa = PBXVariantGroup;
300 | children = (
301 | A7B5418620BF9C400023FE2E /* Base */,
302 | );
303 | name = LaunchScreen.storyboard;
304 | sourceTree = "";
305 | };
306 | /* End PBXVariantGroup section */
307 |
308 | /* Begin XCBuildConfiguration section */
309 | A7B5418920BF9C400023FE2E /* Debug */ = {
310 | isa = XCBuildConfiguration;
311 | buildSettings = {
312 | ALWAYS_SEARCH_USER_PATHS = NO;
313 | CLANG_ANALYZER_NONNULL = YES;
314 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
316 | CLANG_CXX_LIBRARY = "libc++";
317 | CLANG_ENABLE_MODULES = YES;
318 | CLANG_ENABLE_OBJC_ARC = YES;
319 | CLANG_ENABLE_OBJC_WEAK = YES;
320 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
321 | CLANG_WARN_BOOL_CONVERSION = YES;
322 | CLANG_WARN_COMMA = YES;
323 | CLANG_WARN_CONSTANT_CONVERSION = YES;
324 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
325 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
326 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
327 | CLANG_WARN_EMPTY_BODY = YES;
328 | CLANG_WARN_ENUM_CONVERSION = YES;
329 | CLANG_WARN_INFINITE_RECURSION = YES;
330 | CLANG_WARN_INT_CONVERSION = YES;
331 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
332 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
333 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
334 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
335 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
336 | CLANG_WARN_STRICT_PROTOTYPES = YES;
337 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
338 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
339 | CLANG_WARN_UNREACHABLE_CODE = YES;
340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
341 | CODE_SIGN_IDENTITY = "iPhone Developer";
342 | COPY_PHASE_STRIP = NO;
343 | DEBUG_INFORMATION_FORMAT = dwarf;
344 | ENABLE_STRICT_OBJC_MSGSEND = YES;
345 | ENABLE_TESTABILITY = YES;
346 | GCC_C_LANGUAGE_STANDARD = gnu11;
347 | GCC_DYNAMIC_NO_PIC = NO;
348 | GCC_NO_COMMON_BLOCKS = YES;
349 | GCC_OPTIMIZATION_LEVEL = 0;
350 | GCC_PREPROCESSOR_DEFINITIONS = (
351 | "DEBUG=1",
352 | "$(inherited)",
353 | );
354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
356 | GCC_WARN_UNDECLARED_SELECTOR = YES;
357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
358 | GCC_WARN_UNUSED_FUNCTION = YES;
359 | GCC_WARN_UNUSED_VARIABLE = YES;
360 | IPHONEOS_DEPLOYMENT_TARGET = 11.3;
361 | MTL_ENABLE_DEBUG_INFO = YES;
362 | ONLY_ACTIVE_ARCH = YES;
363 | SDKROOT = iphoneos;
364 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
365 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
366 | };
367 | name = Debug;
368 | };
369 | A7B5418A20BF9C400023FE2E /* Release */ = {
370 | isa = XCBuildConfiguration;
371 | buildSettings = {
372 | ALWAYS_SEARCH_USER_PATHS = NO;
373 | CLANG_ANALYZER_NONNULL = YES;
374 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
375 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
376 | CLANG_CXX_LIBRARY = "libc++";
377 | CLANG_ENABLE_MODULES = YES;
378 | CLANG_ENABLE_OBJC_ARC = YES;
379 | CLANG_ENABLE_OBJC_WEAK = YES;
380 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
381 | CLANG_WARN_BOOL_CONVERSION = YES;
382 | CLANG_WARN_COMMA = YES;
383 | CLANG_WARN_CONSTANT_CONVERSION = YES;
384 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
385 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
386 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
387 | CLANG_WARN_EMPTY_BODY = YES;
388 | CLANG_WARN_ENUM_CONVERSION = YES;
389 | CLANG_WARN_INFINITE_RECURSION = YES;
390 | CLANG_WARN_INT_CONVERSION = YES;
391 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
392 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
393 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
394 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
395 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
396 | CLANG_WARN_STRICT_PROTOTYPES = YES;
397 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
398 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
399 | CLANG_WARN_UNREACHABLE_CODE = YES;
400 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
401 | CODE_SIGN_IDENTITY = "iPhone Developer";
402 | COPY_PHASE_STRIP = NO;
403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
404 | ENABLE_NS_ASSERTIONS = NO;
405 | ENABLE_STRICT_OBJC_MSGSEND = YES;
406 | GCC_C_LANGUAGE_STANDARD = gnu11;
407 | GCC_NO_COMMON_BLOCKS = YES;
408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
410 | GCC_WARN_UNDECLARED_SELECTOR = YES;
411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
412 | GCC_WARN_UNUSED_FUNCTION = YES;
413 | GCC_WARN_UNUSED_VARIABLE = YES;
414 | IPHONEOS_DEPLOYMENT_TARGET = 11.3;
415 | MTL_ENABLE_DEBUG_INFO = NO;
416 | SDKROOT = iphoneos;
417 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
418 | VALIDATE_PRODUCT = YES;
419 | };
420 | name = Release;
421 | };
422 | A7B5418C20BF9C400023FE2E /* Debug */ = {
423 | isa = XCBuildConfiguration;
424 | buildSettings = {
425 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
426 | CLANG_ENABLE_MODULES = YES;
427 | CODE_SIGN_IDENTITY = "iPhone Developer";
428 | CODE_SIGN_STYLE = Manual;
429 | DEVELOPMENT_TEAM = 48TB6ZZL5S;
430 | ENABLE_BITCODE = NO;
431 | FRAMEWORK_SEARCH_PATHS = (
432 | "$(inherited)",
433 | "$(PROJECT_DIR)",
434 | "$(PROJECT_DIR)/Agora-Online-PK",
435 | );
436 | INFOPLIST_FILE = "Agora-Online-PK/Info.plist";
437 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
438 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
439 | OTHER_LDFLAGS = "-ObjC";
440 | PRODUCT_BUNDLE_IDENTIFIER = "io.agora.Agora-Online-PK";
441 | PRODUCT_NAME = "$(TARGET_NAME)";
442 | PROVISIONING_PROFILE = "28d072e1-f550-49ef-a601-6038e4fddc51";
443 | PROVISIONING_PROFILE_SPECIFIER = AgoraIODev2018;
444 | SWIFT_OBJC_BRIDGING_HEADER = "";
445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
446 | SWIFT_VERSION = 4.0;
447 | TARGETED_DEVICE_FAMILY = "1,2";
448 | };
449 | name = Debug;
450 | };
451 | A7B5418D20BF9C400023FE2E /* Release */ = {
452 | isa = XCBuildConfiguration;
453 | buildSettings = {
454 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
455 | CLANG_ENABLE_MODULES = YES;
456 | CODE_SIGN_IDENTITY = "iPhone Developer";
457 | CODE_SIGN_STYLE = Manual;
458 | DEVELOPMENT_TEAM = 48TB6ZZL5S;
459 | ENABLE_BITCODE = NO;
460 | FRAMEWORK_SEARCH_PATHS = (
461 | "$(inherited)",
462 | "$(PROJECT_DIR)",
463 | "$(PROJECT_DIR)/Agora-Online-PK",
464 | );
465 | INFOPLIST_FILE = "Agora-Online-PK/Info.plist";
466 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
467 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
468 | OTHER_LDFLAGS = "-ObjC";
469 | PRODUCT_BUNDLE_IDENTIFIER = "io.agora.Agora-Online-PK";
470 | PRODUCT_NAME = "$(TARGET_NAME)";
471 | PROVISIONING_PROFILE = "28d072e1-f550-49ef-a601-6038e4fddc51";
472 | PROVISIONING_PROFILE_SPECIFIER = AgoraIODev2018;
473 | SWIFT_OBJC_BRIDGING_HEADER = "";
474 | SWIFT_VERSION = 4.0;
475 | TARGETED_DEVICE_FAMILY = "1,2";
476 | };
477 | name = Release;
478 | };
479 | /* End XCBuildConfiguration section */
480 |
481 | /* Begin XCConfigurationList section */
482 | A7B5417420BF9C3F0023FE2E /* Build configuration list for PBXProject "Agora-Online-PK" */ = {
483 | isa = XCConfigurationList;
484 | buildConfigurations = (
485 | A7B5418920BF9C400023FE2E /* Debug */,
486 | A7B5418A20BF9C400023FE2E /* Release */,
487 | );
488 | defaultConfigurationIsVisible = 0;
489 | defaultConfigurationName = Release;
490 | };
491 | A7B5418B20BF9C400023FE2E /* Build configuration list for PBXNativeTarget "Agora-Online-PK" */ = {
492 | isa = XCConfigurationList;
493 | buildConfigurations = (
494 | A7B5418C20BF9C400023FE2E /* Debug */,
495 | A7B5418D20BF9C400023FE2E /* Release */,
496 | );
497 | defaultConfigurationIsVisible = 0;
498 | defaultConfigurationName = Release;
499 | };
500 | /* End XCConfigurationList section */
501 | };
502 | rootObject = A7B5417120BF9C3F0023FE2E /* Project object */;
503 | }
504 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Agora-Online-PK
4 | //
5 | // Created by CavanSu on 2018/5/31.
6 | // Copyright © 2018 CavanSu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplicationExtensionPointIdentifier) -> Bool {
23 | return false
24 | }
25 |
26 | func applicationWillResignActive(_ application: UIApplication) {
27 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
28 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
29 | }
30 |
31 | func applicationDidEnterBackground(_ application: UIApplication) {
32 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
33 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
34 | }
35 |
36 | func applicationWillEnterForeground(_ application: UIApplication) {
37 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
38 | }
39 |
40 | func applicationDidBecomeActive(_ application: UIApplication) {
41 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
42 | }
43 |
44 | func applicationWillTerminate(_ application: UIApplication) {
45 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
46 | }
47 |
48 |
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLog.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "agoraLog.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "agoraLog@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "agoraLog@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLog.imageset/agoraLog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLog.imageset/agoraLog.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLog.imageset/agoraLog@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLog.imageset/agoraLog@2x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLog.imageset/agoraLog@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLog.imageset/agoraLog@3x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLogText.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "agoraLogText.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "agoraLogText@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "agoraLogText@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLogText.imageset/agoraLogText.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLogText.imageset/agoraLogText.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLogText.imageset/agoraLogText@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLogText.imageset/agoraLogText@2x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLogText.imageset/agoraLogText@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/agoraLogText.imageset/agoraLogText@3x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/back.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "universal",
9 | "filename" : "back.png",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/back.imageset/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/back.imageset/back.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_button.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "background_button.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "background_button@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "background_button@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_button.imageset/background_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_button.imageset/background_button.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_button.imageset/background_button@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_button.imageset/background_button@2x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_button.imageset/background_button@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_button.imageset/background_button@3x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_room.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "background_room.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "background_room@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "background_room@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_room.imageset/background_room.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_room.imageset/background_room.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_room.imageset/background_room@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_room.imageset/background_room@2x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_room.imageset/background_room@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/background_room.imageset/background_room@3x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/cancel.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "cancel.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "cancel@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "cancel@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/cancel.imageset/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/cancel.imageset/cancel.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/cancel.imageset/cancel@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/cancel.imageset/cancel@2x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/cancel.imageset/cancel@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/cancel.imageset/cancel@3x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "heart.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "heart@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "heart@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart.imageset/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart.imageset/heart.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart.imageset/heart@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart.imageset/heart@2x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart.imageset/heart@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart.imageset/heart@3x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart_room.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "heart_room.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "heart_room@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "heart_room@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart_room.imageset/heart_room.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart_room.imageset/heart_room.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart_room.imageset/heart_room@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart_room.imageset/heart_room@2x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart_room.imageset/heart_room@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/heart_room.imageset/heart_room@3x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/profile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "profile_06.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/profile.imageset/profile_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/profile.imageset/profile_06.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/user_login.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "user_login.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "user_login@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "user_login@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/user_login.imageset/user_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/user_login.imageset/user_login.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/user_login.imageset/user_login@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/user_login.imageset/user_login@2x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/user_login.imageset/user_login@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Agora-Online-PK-iOS/Agora-Online-PK/Assets.xcassets/user_login.imageset/user_login@3x.png
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
39 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
103 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
136 |
147 |
158 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Contants/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // Agora-Online-PK
4 | //
5 | // Created by ZhangJi on 2018/6/4.
6 | // Copyright © 2018 CavanSu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | /// Screen Width
12 | let ScreenWidth = UIScreen.main.bounds.size.width
13 | /// Screen Height
14 | let ScreenHeight = UIScreen.main.bounds.size.height
15 |
16 | /// PK view width
17 | let pkViewWidth = ScreenWidth / 2.0
18 |
19 | /// PK view height
20 | let pkViewHeight = ScreenWidth / 9.0 * 8
21 |
22 | /// RTMP Push URL
23 | let pushUrl = <##YOUR PUBLISH RTMP URL##>;
24 |
25 | /// RTMP Pull URL
26 | let pullUrl = <##YOUR PLAY RTMP URL##>;
27 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Contants/KeyCenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyCenter.swift
3 | // Agora-Online-PK
4 | //
5 | // Created by ZhangJi on 2018/6/4.
6 | // Copyright © 2018 CavanSu. All rights reserved.
7 | //
8 |
9 | struct KeyCenter {
10 | static let AppId: String = <#Your App ID#>
11 | }
12 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Controllers/LoginViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoginViewController.swift
3 | // Agora-Online-PK
4 | //
5 | // Created by ZhangJi on 2018/6/4.
6 | // Copyright © 2018 CavanSu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class LoginViewController: UIViewController {
12 |
13 | /**-----------------------------------------------------------------------------
14 | * This view is uesd to set the channel
15 | *
16 | * In this app we use the channel to identify
17 | * - Agora media channel name
18 | * - Agora RTMP Push URL (Constants.pushUrl + account)
19 | * -----------------------------------------------------------------------------
20 | */
21 | @IBOutlet weak var joinButton: UIButton!
22 | @IBOutlet weak var channelTextField: UITextField!
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | }
27 |
28 | override func didReceiveMemoryWarning() {
29 | super.didReceiveMemoryWarning()
30 | }
31 |
32 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
33 | view.endEditing(true)
34 | }
35 |
36 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
37 | guard let channelName = sender as? String else {
38 | return
39 | }
40 |
41 | let roomVC = segue.destination as! RoomViewController
42 | roomVC.mediaRoomName = channelName
43 | }
44 |
45 | @IBAction func doJoinButtonPressed(_ sender: UIButton) {
46 | guard let channelName = channelTextField.text else {
47 | return
48 | }
49 | if !check(String: channelName) {
50 | return
51 | }
52 | performSegue(withIdentifier: "toRoom", sender: channelName)
53 | }
54 |
55 | func check(String: String) -> Bool {
56 | if String.isEmpty {
57 | AlertUtil.showAlert(message: "The account is empty !")
58 | return false
59 | }
60 |
61 | return true
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Controllers/RoomViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RoomViewController.swift
3 | // Agora-Online-PK
4 | //
5 | // Created by ZhangJi on 2018/6/5.
6 | // Copyright © 2018 CavanSu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AgoraRtcEngineKit
11 |
12 | struct Message {
13 | // struct for message
14 | let name: String!
15 | let content: NSMutableAttributedString!
16 | }
17 |
18 | class RoomViewController: UIViewController {
19 | /**-----------------------------------------------------------------------------
20 | * This view load the mode for Live broadcast
21 | *
22 | * Live broadcast mode:
23 | * You will upload the stream to Agora and CDN you chose
24 | * You can presse PK button to start PK with another broadcast
25 | *
26 | * -----------------------------------------------------------------------------
27 | */
28 | @IBOutlet weak var leaveButton: UIButton!
29 | @IBOutlet weak var pkButton: UIButton!
30 | @IBOutlet weak var endPkButton: UIButton!
31 |
32 | @IBOutlet weak var hostContainView: UIView!
33 |
34 | @IBOutlet weak var urlContainerView: UIView!
35 | @IBOutlet weak var pullUrlLabel: UILabel!
36 | @IBOutlet weak var copyButton: UIButton!
37 |
38 | var agoraKit: AgoraRtcEngineKit!
39 | var myPushUrl: String? // url to push rtmp stream
40 |
41 | var mediaRoomName: String! // channel name to join Agora media room
42 |
43 | var localSession: VideoSession?
44 | var remoteSession: VideoSession?
45 |
46 | var pkRoomeName: String? // channel name for the PK room
47 | var count: Int = 0
48 | var isPk = false {
49 | // the status for PK
50 | didSet {
51 | if isPk != oldValue {
52 | updateViewWithStatus(isPk: isPk)
53 | }
54 | }
55 | }
56 |
57 | override var prefersStatusBarHidden: Bool {
58 | return true
59 | }
60 |
61 | override func viewDidLoad() {
62 | super.viewDidLoad()
63 | }
64 |
65 | override func viewDidAppear(_ animated: Bool) {
66 | super.viewDidAppear(animated)
67 |
68 | setView()
69 |
70 | loadAgoraKit(withIsPk: false)
71 | }
72 |
73 | override func didReceiveMemoryWarning() {
74 | super.didReceiveMemoryWarning()
75 | }
76 |
77 | override func touchesBegan(_ touches: Set, with event: UIEvent?) {
78 | view.endEditing(true)
79 | }
80 |
81 | func setView() {
82 | // init the view
83 | pkButton.layer.borderWidth = 1
84 | pkButton.layer.borderColor = UIColor.white.cgColor
85 | pkButton.layer.cornerRadius = 5
86 | pkButton.layer.masksToBounds = true
87 |
88 | endPkButton.layer.borderWidth = 1
89 | endPkButton.layer.borderColor = UIColor.white.cgColor
90 | endPkButton.layer.cornerRadius = 5
91 | endPkButton.layer.masksToBounds = true
92 | endPkButton.frame.size = CGSize(width: 110, height: 44)
93 | endPkButton.center = CGPoint(x: ScreenWidth / 2.0, y: ScreenHeight / 7 + pkViewHeight)
94 |
95 | copyButton.layer.cornerRadius = 4
96 | copyButton.layer.masksToBounds = true
97 |
98 | urlContainerView.layer.cornerRadius = 4
99 | urlContainerView.layer.masksToBounds = true
100 | }
101 |
102 | func updateViewWithStatus(isPk: Bool) {
103 | // update view with status
104 | self.pkButton.isHidden = isPk
105 | self.endPkButton.isHidden = !isPk
106 | }
107 |
108 | @IBAction func doLeaveButtonPressed(_ sender: UIButton) {
109 | self.isPk = false
110 | self.pkRoomeName = nil
111 | self.leaveAgoraChannel()
112 |
113 | setIdleTimerActive(true)
114 | self.dismiss(animated: true, completion: nil)
115 | }
116 |
117 | @IBAction func doPkButtonPressed(_ sender: UIButton) {
118 | let popView = PopView.newPopViewWith(buttonTitle: "PK", placeholder: "Channel name for PK")
119 | popView?.frame = CGRect(x: 0, y: ScreenHeight, width: ScreenWidth, height: ScreenHeight)
120 | popView?.delegate = self
121 | self.view.addSubview(popView!)
122 | UIView.animate(withDuration: 0.2) {
123 | popView?.frame = self.view.frame
124 | }
125 | }
126 |
127 | @IBAction func doEndPkButtonPressed(_ sender: UIButton) {
128 | self.leaveAgoraChannel()
129 | }
130 |
131 | @IBAction func doCopyPressed(_ sender: UIButton) {
132 | // Copy url
133 | UIPasteboard.general.string = pullUrl + self.mediaRoomName
134 | }
135 |
136 | func setIdleTimerActive(_ active: Bool) {
137 | UIApplication.shared.isIdleTimerDisabled = !active
138 | }
139 | }
140 |
141 | // MARK: - AgoraMedia
142 | private extension RoomViewController {
143 | func loadAgoraKit(withIsPk status: Bool) {
144 | // load agora media kit and join media channel with PK status, only the broadcaster will join agora
145 | // the agora media channel, the audience just join agora signal channel for channel chat
146 | agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: KeyCenter.AppId, delegate: self)
147 | agoraKit.setChannelProfile(.liveBroadcasting)
148 | agoraKit.setClientRole(.broadcaster)
149 | agoraKit.enableVideo()
150 | agoraKit.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: AgoraVideoDimension640x360,
151 | frameRate: .fps15,
152 | bitrate: AgoraVideoBitrateStandard,
153 | orientationMode: .adaptative))
154 |
155 | agoraKit.startPreview()
156 | self.addLocalSession()
157 | UIView.animate(withDuration: 0.2) {
158 | self.localSession?.hostingView.frame = status ? CGRect(x: 0, y: ScreenHeight / 7, width: pkViewWidth, height: pkViewHeight) : self.hostContainView.frame
159 | }
160 |
161 | let code = agoraKit.joinChannel(byToken: nil, channelId: isPk ? self.pkRoomeName! : self.mediaRoomName, info: nil, uid: 0, joinSuccess: nil)
162 | if code == 0 {
163 | setIdleTimerActive(false)
164 | agoraKit.setEnableSpeakerphone(true)
165 | }
166 |
167 | }
168 |
169 | func addLocalSession() {
170 | if self.localSession == nil {
171 | self.localSession = VideoSession.localSession()
172 | }
173 | agoraKit.setupLocalVideo(localSession?.canvas)
174 | self.hostContainView.addSubview((localSession?.hostingView)!)
175 | }
176 |
177 | /**-----------------------------------------------------------------------------
178 | *
179 | * Key Code for Live transcode
180 | *
181 | * -----------------------------------------------------------------------------
182 | */
183 | func updateLiveTranscoding(withMenber menber: Int) {
184 | // LiveTranscoding update, the LiveTranscoding is used to set the CDN stream layout in Agora server
185 | // more details please refer to the document
186 | switch menber {
187 | case 1:
188 | // the LiveTranscoding for one person
189 | let localUser = AgoraLiveTranscodingUser()
190 | localUser.uid = (self.localSession?.uid)!
191 | localUser.rect = CGRect(x: 0, y: 0, width: 360, height: 640)
192 | localUser.zOrder = 1
193 | localUser.audioChannel = 0
194 |
195 | let liveTranscoding = AgoraLiveTranscoding()
196 |
197 | liveTranscoding.transcodingUsers = [localUser]
198 | liveTranscoding.size = CGSize(width: 360, height: 640)
199 | liveTranscoding.videoBitrate = 1200
200 | liveTranscoding.videoFramerate = 15
201 | liveTranscoding.backgroundColor = UIColor.clear
202 |
203 | agoraKit.setLiveTranscoding(liveTranscoding)
204 | case 2:
205 | // the LiveTranscoding for two persons in PK mode
206 | var uses = [AgoraLiveTranscodingUser]()
207 | let localUser = AgoraLiveTranscodingUser()
208 | localUser.uid = (self.localSession?.uid)!
209 | localUser.rect = CGRect(x: 0, y: 0, width: 360, height: 640)
210 | localUser.zOrder = 1
211 | localUser.audioChannel = 0
212 | uses.append(localUser)
213 |
214 | if self.remoteSession != nil {
215 | let removeUser = AgoraLiveTranscodingUser()
216 | removeUser.uid = (self.remoteSession?.uid)!
217 | removeUser.rect = CGRect(x: 360, y: 0, width: 360, height: 640)
218 | removeUser.zOrder = 1
219 | removeUser.audioChannel = 0
220 | uses.append(removeUser)
221 | }
222 |
223 | let liveTranscoding = AgoraLiveTranscoding()
224 | liveTranscoding.transcodingUsers = uses
225 | liveTranscoding.size = CGSize(width: 720, height: 640)
226 | liveTranscoding.videoBitrate = 1200
227 | liveTranscoding.videoFramerate = 15
228 | liveTranscoding.backgroundColor = UIColor.clear
229 |
230 | agoraKit.setLiveTranscoding(liveTranscoding)
231 | default:
232 | break
233 | }
234 | }
235 |
236 | func leaveAgoraChannel() {
237 | // leave agora media channel
238 | if let myPushUrl = self.myPushUrl {
239 | agoraKit.removePublishStreamUrl(myPushUrl)
240 | }
241 | agoraKit.setupLocalVideo(nil)
242 | agoraKit.leaveChannel(nil)
243 | }
244 | }
245 |
246 | // MARK: - AgoraMedia Deleagte
247 | extension RoomViewController: AgoraRtcEngineDelegate {
248 | func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
249 | print("join media channel: \(channel)")
250 | self.localSession?.uid = uid
251 |
252 | self.updateLiveTranscoding(withMenber: self.isPk ? 2 : 1)
253 | self.myPushUrl = pushUrl + self.mediaRoomName
254 | self.pullUrlLabel.text = pullUrl + self.mediaRoomName
255 | agoraKit.addPublishStreamUrl(self.myPushUrl!, transcodingEnabled: true)
256 | }
257 |
258 | func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
259 | if isPk && count < 1 {
260 | remoteSession = VideoSession(uid: uid)
261 | agoraKit.setupRemoteVideo((remoteSession?.canvas)!)
262 | remoteSession?.hostingView.frame = CGRect(x: pkViewWidth, y: ScreenHeight / 7, width: pkViewWidth, height: pkViewHeight)
263 | self.hostContainView.addSubview((remoteSession?.hostingView)!)
264 | count = 1
265 | self.updateLiveTranscoding(withMenber: 2)
266 | }
267 | }
268 |
269 | func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
270 | if uid == remoteSession?.uid {
271 | remoteSession?.hostingView.removeFromSuperview()
272 | remoteSession = nil
273 | count = 0
274 | self.leaveAgoraChannel()
275 | }
276 | }
277 |
278 | func rtcEngine(_ engine: AgoraRtcEngineKit, streamPublishedWithUrl url: String, errorCode: AgoraErrorCode) {
279 | print("streamPublishedWithUrl: error \(errorCode.rawValue)")
280 | }
281 |
282 | func rtcEngine(_ engine: AgoraRtcEngineKit, didLeaveChannelWith stats: AgoraChannelStats) {
283 | print("leave channel")
284 | guard let _ = self.pkRoomeName else {
285 | return
286 | }
287 | if isPk {
288 | // if in PK mode, the broadcaster will first leave the PK room, then go back his own room
289 | self.isPk = false
290 | self.pkRoomeName = nil
291 | self.remoteSession?.hostingView.removeFromSuperview()
292 | loadAgoraKit(withIsPk: false)
293 | } else {
294 | // if it's not in PK mode, the broadcaster will first leave his owm roonm, then join the PK room
295 | self.isPk = true
296 | loadAgoraKit(withIsPk: true)
297 | }
298 | }
299 | }
300 |
301 | extension RoomViewController: PopViewDelegate {
302 | func popViewButtonDidPressed(_ popView: PopView) {
303 | guard let pkRoomName = popView.inputTextField.text else {
304 | return
305 | }
306 | if !check(String: pkRoomName) {
307 | return
308 | }
309 | self.pkRoomeName = pkRoomName
310 | self.leaveAgoraChannel()
311 | popView.removeFromSuperview()
312 | }
313 |
314 | func check(String: String) -> Bool {
315 | if String.isEmpty {
316 | AlertUtil.showAlert(message: "The account is empty !")
317 | return false
318 | }
319 | return true
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | NSAppTransportSecurity
24 |
25 | NSAllowsArbitraryLoads
26 |
27 |
28 | NSCameraUsageDescription
29 |
30 | NSMicrophoneUsageDescription
31 |
32 | UIFileSharingEnabled
33 |
34 | UILaunchStoryboardName
35 | LaunchScreen
36 | UIMainStoryboardFile
37 | Main
38 | UIRequiredDeviceCapabilities
39 |
40 | armv7
41 |
42 | UISupportedInterfaceOrientations
43 |
44 | UIInterfaceOrientationPortrait
45 |
46 | UISupportedInterfaceOrientations~ipad
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationPortraitUpsideDown
50 | UIInterfaceOrientationLandscapeLeft
51 | UIInterfaceOrientationLandscapeRight
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Utils/AlertUtil.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlertUtil.swift
3 | // AgoraHQ
4 | //
5 | // Created by ZhangJi on 09/01/2018.
6 | // Copyright © 2018 ZhangJi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class AlertUtil: NSObject {
12 | static func showAlert(message: String) {
13 | let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
14 | alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
15 |
16 | let rootVC = UIApplication.shared.keyWindow?.rootViewController
17 | let topVC = rootVC?.presentedViewController != nil ? rootVC?.presentedViewController : rootVC
18 |
19 | DispatchQueue.main.async {
20 | topVC?.present(alert, animated: true, completion: nil)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Utils/CommonExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommonExtensions.swift
3 | // OpenVideoCall
4 | //
5 | // Created by ZhangJi on 22/08/2017.
6 | // Copyright © 2017 ZhangJi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor {
12 | convenience init(hex: Int, alpha: CGFloat = 1) {
13 | func transform(_ input: Int, offset: Int = 0) -> CGFloat {
14 | let value = (input >> offset) & 0xff
15 | return CGFloat(value) / 255
16 | }
17 |
18 | self.init(red: transform(hex, offset: 16),
19 | green: transform(hex, offset: 8),
20 | blue: transform(hex),
21 | alpha: alpha)
22 | }
23 | }
24 |
25 | extension CGSize {
26 | func fixedSize(with reference: CGSize) -> CGSize {
27 | if reference.width > reference.height {
28 | return fixedLandscapeSize()
29 | } else {
30 | return fixedPortraitSize()
31 | }
32 | }
33 |
34 | func fixedLandscapeSize() -> CGSize {
35 | let width = self.width
36 | let height = self.height
37 | if width < height {
38 | return CGSize(width: height, height: width)
39 | } else {
40 | return self
41 | }
42 | }
43 |
44 | func fixedPortraitSize() -> CGSize {
45 | let width = self.width
46 | let height = self.height
47 | if width > height {
48 | return CGSize(width: height, height: width)
49 | } else {
50 | return self
51 | }
52 | }
53 |
54 | func fixedSize() -> CGSize {
55 | let width = self.width
56 | let height = self.height
57 | if width < height {
58 | return CGSize(width: height, height: width)
59 | } else {
60 | return self
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Views/PopView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PopView.swift
3 | // Agora-Online-PK
4 | //
5 | // Created by ZhangJi on 2018/6/5.
6 | // Copyright © 2018 CavanSu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @objc protocol PopViewDelegate: NSObjectProtocol {
12 | func popViewButtonDidPressed(_ popView: PopView)
13 |
14 | @objc optional func popViewDidRemoved(_ popView: PopView)
15 | }
16 |
17 | class PopView: UIView {
18 | // this is a custom tool for pop input view
19 | @IBOutlet weak var inputTextField: UITextField!
20 | @IBOutlet weak var popViewButton: UIButton!
21 |
22 | weak var delegate: PopViewDelegate?
23 |
24 | static func newPopViewWith(buttonTitle: String, placeholder: String) -> PopView? {
25 | let nibView = Bundle.main.loadNibNamed("PopView", owner: nil, options: nil)
26 | if let view = nibView?.first as? PopView {
27 | let attDic = NSMutableDictionary()
28 | attDic[NSAttributedStringKey.foregroundColor] = UIColor.lightGray
29 | let attPlaceholder = NSAttributedString(string: placeholder, attributes: attDic as? [NSAttributedStringKey : Any])
30 | view.inputTextField.attributedPlaceholder = attPlaceholder
31 | view.popViewButton.setTitle(buttonTitle, for: .normal)
32 | return view
33 | }
34 | return nil
35 | }
36 |
37 | override init(frame: CGRect) {
38 | super.init(frame: frame)
39 | }
40 |
41 | required init?(coder aDecoder: NSCoder) {
42 | super.init(coder: aDecoder)
43 | }
44 |
45 | @IBAction func doCancelButton(_ sender: UIButton) {
46 | if delegate != nil {
47 | delegate?.popViewDidRemoved?(self)
48 | }
49 | self.removeFromSuperview()
50 | }
51 |
52 | @IBAction func doPopViewButtonPressed(_ sender: UIButton) {
53 | if delegate != nil {
54 | delegate?.popViewButtonDidPressed(self)
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Views/PopView.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
39 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/Agora-Online-PK/Views/VideoSession.swift:
--------------------------------------------------------------------------------
1 | //
2 | // VideoSession.swift
3 | // Agora-Online-PK
4 | //
5 | // Created by ZhangJi on 2018/6/4.
6 | // Copyright © 2018 CavanSu. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AgoraRtcEngineKit
11 |
12 | class VideoSession: NSObject {
13 | // this is a custom object for agora video view
14 | var uid: UInt = 0
15 | var hostingView: UIView!
16 | var canvas: AgoraRtcVideoCanvas!
17 |
18 | init(uid: UInt) {
19 | self.uid = uid
20 |
21 | hostingView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
22 | hostingView.translatesAutoresizingMaskIntoConstraints = false
23 |
24 | canvas = AgoraRtcVideoCanvas()
25 | canvas.uid = UInt(uid)
26 | canvas.view = hostingView
27 | canvas.renderMode = .hidden
28 | }
29 | }
30 |
31 | extension VideoSession {
32 | static func localSession() -> VideoSession {
33 | return VideoSession(uid: 0)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Agora-Online-PK-iOS/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Agora.io
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 |
--------------------------------------------------------------------------------
/Image/API_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Image/API_list.png
--------------------------------------------------------------------------------
/Image/API_list_EN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Image/API_list_EN.png
--------------------------------------------------------------------------------
/Image/ArchitectureDesign.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Image/ArchitectureDesign.png
--------------------------------------------------------------------------------
/Image/ArchitectureDesign_EN.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AgoraIO-Usecase/Online-PK/f290ed7dc79649455953afb790d4c78699cc3ebf/Image/ArchitectureDesign_EN.png
--------------------------------------------------------------------------------
/Image/descritpion.md:
--------------------------------------------------------------------------------
1 | This folder contains all the images required in the readme
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Online PK
2 |
3 | *Other Languages: [中文](README.zh.md)*
4 |
5 | ## **Agora PK Hosting**
6 |
7 | The Agora PK Hosting solution is designed for CDN Live push-and-pull stream scenarios that involve switching between the following scenarios:
8 |
9 | - Co-hosting in Standard mode
10 | - Co-hosting in PK mode
11 |
12 | ## Co-hosting in Standard Mode
13 |
14 | The hosts can use third-party applications such as ijkplayer to push streams to CDN Live. The push stream address for the hosts are independent and the audience can only see the corresponding host.
15 |
16 | ## Co-hosting in PK Mode
17 |
18 | The hosts need to quit the CDN Live push stream process, join the same Agora channel, and set the co-hosting composite mode on the Agora server using the _setLiveTranscoding_ API method; then push the composite stream to the original CDN address using the push stream _addPublishStreamUrl_ API method.
19 |
20 | The CDN Live audience can then participate in the PK between the hosts. The CDN Live audience does not need to change the CDN Live URL address as the hosts will still use the previous CDN Live push stream URL address. When either one of the hosts quit the Agora channel, the other host will switch to the Standard mode.
21 |
22 | ## **Architectural Design**
23 | 
24 |
25 | You can find the Agora [implementation code](https://github.com/AgoraIO/ARD-Agora-Online-PK/tree/master/Agora-Online-PK-Android) for Android on Github. You can also download the [APK file](https://github.com/AgoraIO-Usecase/Online-PK/releases/download/v1.0/Agora-PK-Online.apk).
26 |
27 | ## **API methods**
28 |
29 | 
30 |
31 | The API methods related to the Agora Online PK:
32 |
33 | iOS|Android
34 | ---|---
35 | [sharedEngineWithAppId:delegate:](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/sharedEngineWithAppId:delegate:)|[create](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a35466f690d0a9332f24ea8280021d5ed)
36 | [setChannelProfile](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/setChannelProfile:)|[setChannelProfile](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a1bfb76eb4365b8b97648c3d1b69f2bd6)
37 | [setClientRole](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/setClientRole:)|[setClientRole](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#aa2affa28a23d44d18b6889fba03f47ec)
38 | [enableVideo](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/enableVideo)|[enableVideo](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a99ae52334d3fa255dfcb384b78b91c52)
39 | [joinChannel](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/joinChannelByToken:channelId:info:uid:joinSuccess:)|[joinChannel](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a8b308c9102c08cb8dafb4672af1a3b4c)
40 | [setLiveTranscoding](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_ios?platform=iOS#livetranscoding-ios)|[setLiveTranscoding](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_android?platform=Android#setlivetranscoding)
41 | [addPublishStreamUrl](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_ios?platform=iOS#addpublishstreamurl-transcodingenabled)|[addPublishStreamUrl](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_android?platform=Android#addpublishstreamurl)
42 | [removePublishStreamUrl](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_ios?platform=iOS#removepublishstreamurl)|[removePublishStreamUrl](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_android?platform=Android#removepublishstreamurl)
43 | [leaveChannel](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/leaveChannel:)|[leaveChannel](https://docs.agora.io/en/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a2929e4a46d5342b68d0deb552c29d597)
44 |
45 | ## **Implementation**
46 |
47 | - The Agora PK Hosting solution uses the Agora Video SDK in the communication mode.
48 |
49 | - When switching to the PK mode from the Standard mode, each host needs to quit the original CDN Live stream and join the same Agora channel through the application logic.
50 |
51 | - Under the PK mode:
52 | 1. Each host needs to set the composite configuration using the _setLiveTranscoding API method a_nd add the CDN Live push stream URL address, using the _addPublishStreamUrl API method,_ in the Agora channel.
53 | 2. The hosts need to ensure that the CDN Live push stream URL address will not change after switching from the Standard mode.
54 | 3. When either one of the hosts quit the Agora channel, the other host will quit the channel and switch to the Standard mode through the application.
55 |
56 | - Before switching to the Standard mode from the PK mode:
57 | 1. Each host needs to remove the previous CDN Live push stream URL address using the _removePublishStreamUrl API_ method.
58 | 2. Each host needs to push the stream to the original CDN URL address.
59 |
60 |
61 | ## **Integration Guide**
62 |
63 | ### Integration SDK
64 |
65 | - For Android, see [Configuring the DEV runtime](https://docs.agora.io/en/Interactive%20Broadcast/android_video?platform=Android).
66 | - For IOS, see [Configuring the DEV runtime.](https://docs.agora.io/en/Interactive%20Broadcast/ios_video?platform=iOS)
67 |
68 | ### Switching Between the Co-hosting Standard Mode and Co-hosting PK Mode
69 |
70 | _Android_:
71 |
72 | 1. [Video broadcasting realization](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/Quickstart%20Guide/broadcast_video_android?platform=Android)
73 | 2. [Stream pushing to CDN Live](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/Quickstart%20Guide/push_stream_android2.0?platform=Android)
74 | 3. Call the [_removePublishStreamUrl_](https://docs.agora.io/en/2.4/product/Interactive%20Broadcast/API%20Reference/live_video_android?platform=Android) API method to remove the stream URL address.
75 |
76 | _IOS_:
77 |
78 | 1. [Video broadcasting realization](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/Quickstart%20Guide/broadcast_video_ios?platform=iOS)
79 |
80 | 1. [Stream pushing to CDN Live](https://docs.agora.io/en/2.3.1/product/Interactive%20Broadcast/Quickstart%20Guide/push_stream_ios2.0?platform=iOS)
81 | 2. Call the [_removePublishStreamUrl_](https://docs.agora.io/en/2.4/product/Interactive%20Broadcast/API%20Reference/live_video_ios?platform=iOS) API method to remove the stream URL address.
82 |
83 | Ijkplayer Realization (Optional)
84 |
85 | Android: See ['Integration of ijkplayer framework for Android development'](https://github.com/Bilibili/ijkplayer).
86 |
--------------------------------------------------------------------------------
/README.zh.md:
--------------------------------------------------------------------------------
1 | # PK 连麦
2 |
3 | *Other Languages: [English](README.md)*
4 |
5 | 声网 PK 连麦方案场景针对 CDN 推流拉流场景设计,包含以下内容:
6 |
7 | * 场景描述
8 | * 架构设计
9 | * 集成步骤
10 | * 集成注意事项
11 |
12 | ## 场景描述
13 |
14 | 声网 PK 连麦场景针对 CDN 推流场景设计,主要涉及两种模式的切换:
15 |
16 | * 单主播模式
17 | * 双主播 PK 模式
18 | * 观众模式(可选)
19 |
20 | ### 单主播模式
21 |
22 | 主播可以采用Agora SDK 或者第三方推流工具(比如 ijkplayer)推流到 CDN。观众通过 CDN 播放器拉取主播视频流观看直播。
23 |
24 | ### 双主播 PK 模式
25 |
26 | 两个主播退出 CDN 推流并同时加入同一个声网频道,各自在声网服务端设置双主播的合图(setLiveTranscoding),并通过声网提供的推流接口(addPublishStreamUrl)将合图后的流推送到原先的 CDN 地址。各自的 CDN 观众看到两个主播开始 PK。由于两个 PK 主播各自的 CDN 推流地址未发生改变,CDN 观众端不需要切换 CDN 拉流地址。只要任意一个主播离开声网 PK 频道,另一主播也退出 PK 模式返回普通模式。
27 |
28 | ### 观众模式(可选)
29 |
30 | 观众一般使用第三方 CDN 播放器(比如 ijkplayer)拉取视频流观看直播,本示例程序中并不包含观众模式。
31 |
32 | ## 架构设计
33 |
34 | 下图为一起 PK 连麦场景的声网实现架构图:
35 |
36 | 
37 |
38 | 声网已在 GitHub 提供了 Android 平台的 [实现代码](https://github.com/AgoraIO/ARD-Agora-Online-PK/tree/master/Agora-Online-PK-Android)。你也可以下载实现的 [apk 文件](https://github.com/AgoraIO-Usecase/Online-PK/releases/download/v1.0/Agora-PK-Online.apk)。
39 |
40 | ## API 列表
41 | 示例 App 的 API 流程如下图所示。
42 | 
43 |
44 | Agora SDK 关键 API 列表:
45 |
46 | iOS|Android
47 | ---|---
48 | [sharedEngineWithAppId:delegate:](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/sharedEngineWithAppId:delegate:)|[create](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a35466f690d0a9332f24ea8280021d5ed)
49 | [setChannelProfile](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/setChannelProfile:)|[setChannelProfile](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a1bfb76eb4365b8b97648c3d1b69f2bd6)
50 | [setClientRole](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/setClientRole:)|[setClientRole](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#aa2affa28a23d44d18b6889fba03f47ec)
51 | [enableVideo](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/enableVideo)|[enableVideo](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a99ae52334d3fa255dfcb384b78b91c52)
52 | [joinChannel](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/joinChannelByToken:channelId:info:uid:joinSuccess:)|[joinChannel](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a8b308c9102c08cb8dafb4672af1a3b4c)
53 | [setLiveTranscoding](https://docs.agora.io/cn/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_ios?platform=iOS#livetranscoding-ios)|[setLiveTranscoding](https://docs.agora.io/cn/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_android?platform=Android#setlivetranscoding)
54 | [addPublishStreamUrl](https://docs.agora.io/cn/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_ios?platform=iOS#addpublishstreamurl-transcodingenabled)|[addPublishStreamUrl](https://docs.agora.io/cn/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_android?platform=Android#addpublishstreamurl)
55 | [removePublishStreamUrl](https://docs.agora.io/cn/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_ios?platform=iOS#removepublishstreamurl)|[removePublishStreamUrl](https://docs.agora.io/cn/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_android?platform=Android#removepublishstreamurl)
56 | [leaveChannel](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/leaveChannel:)|[leaveChannel](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a2929e4a46d5342b68d0deb552c29d597)
57 |
58 | ## 集成步骤
59 |
60 | ### PK 连麦方案场景实现
61 |
62 | **PK 场景需要实现以下功能:**
63 |
64 | 1. 集成声网 SDK 实现视频直播
65 | * Android, 详见 [集成客户端](https://docs.agora.io/cn/Interactive%20Broadcast/start_live_android?platform=Android)
66 | * iOS, 详见 [集成客户端](https://docs.agora.io/cn/Interactive%20Broadcast/start_live_ios?platform=iOS)
67 |
68 | 2. 实现 CDN 推流,和服务器合图
69 | * Android, 详见 [推流到 CDN ](https://docs.agora.io/cn/Interactive%20Broadcast/cdn_streaming_android?platform=Android)
70 | * iOS, 详见 [推流到 CDN ](https://docs.agora.io/cn/Interactive%20Broadcast/cdn_streaming_apple?platform=iOS)
71 |
72 | 3. 实现第三方推流,拉流(可选)
73 | * 如单主播模式需要使用第三方推流工具,需要自行集成第三方推流。
74 | * 如 APP 需要实现观众模式,需要自行集成第三方拉流播放器。
75 |
76 | ### 实现细节
77 |
78 | * 声网 PK 连麦方案采用直播模式的 Agora Video SDK。
79 | * 从单主播模式进入 PK 模式时,每个主播都需要退出原来的旁路推流。
80 | * 从单主播模式进入 PK 模式时,各位主播需要同时加入同一声网频道,可由 APP 控制实现。
81 | * PK 模式下,每个主播都需要设置合图(setLiveTranscoding,[Android](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a3cb9804ae71819038022d7575834b88c), [iOS](https://docs.agora.io/cn/2.3.1/product/Interactive%20Broadcast/API%20Reference/live_video_ios?platform=iOS#livetranscoding-ios)), 并重新添加 CDN 推流地址(addPublishStreamUrl,[Android](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a4445b4ca9509cc4e2966b6d308a8f08f), [iOS](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/addPublishStreamUrl:transcodingEnabled:))
82 | * PK 模式下,设置合图(setLiveTranscoding)和添加 CDN 推流地址(addPublishStreamUrl)需要在声网频道内进行。
83 | * PK 模式下,双方主播的 CDN 推流地址应与普通模式时选用的 URL 地址一致确保 CDN 观众无需切换 CDN 地址。
84 | * PK 模式下,只要有一位主播退出声网频道,其余主播也同时退出声网频道进入单主播模式,可由 APP 控制实现。
85 | * 从 PK 模式进入单主播模式前,每个主播都需要移除原先的 CDN 推流地址(removePublishStreamUrl,[Android](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/java/classio_1_1agora_1_1rtc_1_1_rtc_engine.html#a87b3f2f17bce8f4cc42b3ee6312d30d4), [iOS](https://docs.agora.io/cn/Interactive%20Broadcast/API%20Reference/oc/Classes/AgoraRtcEngineKit.html#//api/name/removePublishStreamUrl:))。
86 | * 从 PK 模式进入普通模式时,每个主播需要重新向原来的 CDN 地址推流确保 CDN 观众无需切换地址。
87 |
88 | ### 运行示例程序
89 |
90 | 首先在 [Agora.io 注册](https://dashboard.agora.io/cn/signup/) 注册账号,并创建自己的测试项目,获取到 AppID。
91 | 然后在 [Agora.io SDK](https://docs.agora.io/cn/Interactive%20Broadcast/downloads) 下载 视频通话/视频直播 SDK
92 |
93 | **iOS**
94 |
95 | 1. 将 AppID 填写进 KeyCenter.m
96 |
97 | ```
98 | static let AppId: String = "Your App ID"
99 | ```
100 |
101 | 2. 解压视频通话/视频直播 SDK 包,将其中的 libs/AgoraRtcEngineKit.framework 复制到项目文件夹下。
102 | 3. 最后使用 XCode 打开 Agora-Online-PK.xcodeproj,连接 iPhone/iPad 测试设备,设置有效的开发者签名后即可运行。
103 |
104 | ```
105 | * XCode 9.0 +
106 | * iOS 9.0 真机设备
107 | * 不支持模拟器
108 | ```
109 |
110 | **Android**
111 |
112 | 1. 将 AppID 填写进 PKConstants 的 MEDIA_APP_ID以及SIGNALING_APP_ID 中
113 | 2. 解压视频通话/视频直播 SDK 包,将其中的jar和so复制到项目对应文件夹下。
114 | 3. 最后使用 AndroidStudio 打开项目,连接 Android 测试设备,编译并运行。
115 |
116 | ```
117 | * Android Studio 2.0 +
118 | * minSdkVersion 16
119 | * 部分模拟器会存在功能缺失或者性能问题,所以推荐使用真机设备
120 | ```
121 |
122 | ### 示例程序功能
123 | - 开始直播:在首页输入直播频道名,点击“开始直播”按钮,进入直播房间,开始直播和 CDN 推流;
124 | - 发起PK:在房间内点击“PK”按钮,并输入“PK房间名”进入PK(需要两个主播同时输入相同的“PK房间名”以进入同一房间);
125 | - 退出PK:点击“退出PK”按钮,退出PK模式,返回单主播模式;
126 | - 退出房间:点击右上角“离开”按钮,离开直播房间;
127 | - 拷贝拉流地址:在直播中可点击“拷贝”按钮,拷贝拉流地址,使用 CDN 播放器(如 VLC)
128 |
129 | ## 集成注意事项
130 | - 单主播模式与 PK 模式切换时一定要先停止原先的推流,再重新开始推流,否则会推流失败
131 | - 观众一般需要感知主播模式的改变来更新UI,一般是通过信令通知观众,由于信令和 CDN 视频流存在时间差(CDN 推流一般存在数秒的延迟),为了更好的用户体验,可以在切换模式时做一个延时动画,让用户忽略这个时间差。也可以通过 CDN 播放器的特有回调(如视频尺寸改变)来感知主播状态的变化。
132 |
133 | ## 联系我们
134 |
135 | - 如果发现了示例程序的 bug,欢迎提交 [issue](https://github.com/AgoraIO/ARD-Agora-Online-PK/issues)
136 | - 声网 SDK 完整 API 文档见 [文档中心](https://docs.agora.io/cn/)
137 | - 如果在集成中遇到问题,你可以到 [开发者社区](https://dev.agora.io/cn/) 提问
138 | - 如果有售前咨询问题,可以拨打 400 632 6626,或加入官方Q群 12742516 提问
139 | - 如果需要售后技术支持,你可以在 [Agora Dashboard](https://dashboard.agora.io) 提交工单
140 |
141 | ## 代码许可
142 |
143 | The MIT License (MIT).
144 |
--------------------------------------------------------------------------------