├── .gitignore
├── LICENSE
├── LICENSE-Apache
├── README.md
├── app_live
├── .gitignore
├── README.md
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── duqian
│ │ └── app
│ │ ├── MainActivity.kt
│ │ ├── base
│ │ ├── BaseActivity.kt
│ │ ├── BaseApplication.kt
│ │ └── BaseFragment.kt
│ │ ├── helper
│ │ ├── CommonUtils.kt
│ │ ├── EventBusHelper.kt
│ │ ├── PageRecordHelper.kt
│ │ ├── ToastUtils.kt
│ │ └── UEToolHelper.kt
│ │ ├── live
│ │ ├── base_comm
│ │ │ ├── BaseController.kt
│ │ │ ├── BaseDataBindingDialog.kt
│ │ │ ├── BaseDialogFragment.kt
│ │ │ ├── BaseEvent.kt
│ │ │ ├── BaseLiveActivity.kt
│ │ │ ├── BaseLiveFragment.kt
│ │ │ ├── BaseRoomParams.kt
│ │ │ └── RoomConstants.kt
│ │ ├── controller
│ │ │ ├── AnchorInfoController.kt
│ │ │ ├── RoomChatBtnController.kt
│ │ │ ├── RoomCommonController.kt
│ │ │ └── RoomGiftBtnController.kt
│ │ ├── fragment
│ │ │ ├── PublicChatFragment.kt
│ │ │ ├── RoomBottomBtnFragment.kt
│ │ │ ├── RoomEffectFragment.kt
│ │ │ ├── RoomMainFragment.kt
│ │ │ ├── RoomTestFragment.kt
│ │ │ └── dialog
│ │ │ │ └── RoomGiftDialog.kt
│ │ ├── liveroom
│ │ │ ├── LiveRoomActivity.kt
│ │ │ ├── controller
│ │ │ │ ├── RoomCallBtnController.kt
│ │ │ │ ├── RoomCoinsBtnController.kt
│ │ │ │ └── RoomLivePlayerController.kt
│ │ │ └── data
│ │ │ │ ├── LiveRoomEvent.kt
│ │ │ │ └── LiveRoomParams.kt
│ │ ├── pushroom
│ │ │ ├── PushRoomActivity.kt
│ │ │ ├── controller
│ │ │ │ ├── AgoraPusherController.kt
│ │ │ │ ├── PushCameraController.kt
│ │ │ │ └── PushMicBtnController.kt
│ │ │ ├── data
│ │ │ │ ├── PushRoomEvent.kt
│ │ │ │ └── PushRoomParams.kt
│ │ │ └── ui
│ │ │ │ └── LivePreviewFragment.kt
│ │ ├── repository
│ │ │ ├── RoomApiImpl.kt
│ │ │ └── RoomModule.kt
│ │ ├── services
│ │ │ ├── AutoServiceHelper.kt
│ │ │ └── IRoomApi.kt
│ │ ├── utils
│ │ │ ├── FragmentUtils.kt
│ │ │ ├── KotlinExtUtils.kt
│ │ │ ├── LiveEventBus.kt
│ │ │ └── UIUtil.kt
│ │ └── viewmodel
│ │ │ ├── GlobalViewModel.kt
│ │ │ └── RoomGlobalViewModel.kt
│ │ ├── main
│ │ ├── MainFragment.kt
│ │ └── MainViewModel.kt
│ │ └── navigator
│ │ ├── AppNavigator.kt
│ │ ├── AppNavigatorImpl.kt
│ │ └── NavigationModule.kt
│ └── res
│ ├── anim
│ └── loading.xml
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ ├── ic_launcher_background.xml
│ ├── shape_live_room_bg.xml
│ ├── shape_live_top_anchor_bg.xml
│ └── shape_push_start_live_bg.xml
│ ├── layout
│ ├── activity_live_push_room.xml
│ ├── activity_live_room.xml
│ ├── activity_main.xml
│ ├── dialog_room_gift_layout.xml
│ ├── fragment_liveroom_bottom_btns.xml
│ ├── fragment_main.xml
│ ├── fragment_pushroom_bottom_btns.xml
│ ├── fragment_pushroom_preview.xml
│ ├── fragment_room_gift_effect.xml
│ ├── fragment_room_main.xml
│ ├── fragment_room_public_chat.xml
│ ├── fragment_room_test_layout.xml
│ ├── include_liveroom_player.xml
│ ├── include_pushroom_preview.xml
│ ├── include_room_close_layout.xml
│ ├── include_room_common_layout.xml
│ ├── include_room_public_chat_layout.xml
│ └── include_room_top_layout.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-mdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xhdpi
│ ├── ic_launcher.webp
│ └── ic_launcher_round.webp
│ ├── mipmap-xxhdpi
│ ├── bg_dq_live_splash.jpeg
│ ├── bg_live_common_btn.webp
│ ├── bg_live_pushroom_go_live.webp
│ ├── bg_live_room_loading_default.webp
│ ├── ic_launcher.webp
│ ├── ic_launcher_round.webp
│ ├── ic_live_close_room.webp
│ ├── ic_live_pushroom_btn_mic_off.webp
│ ├── ic_live_pushroom_btn_mic_on.webp
│ ├── ic_live_room_anchor_follow.webp
│ ├── ic_live_room_btn_call.png
│ ├── ic_live_room_btn_chat.webp
│ ├── ic_live_room_btn_coins.webp
│ ├── ic_live_room_btn_coins_activity.webp
│ ├── ic_live_room_btn_gift.webp
│ ├── ic_live_room_hot_num.webp
│ └── ic_live_room_loading_circle.webp
│ ├── mipmap-xxxhdpi
│ ├── bg_room_gift_shop.webp
│ ├── ic_launcher.webp
│ ├── ic_launcher_round.webp
│ ├── ic_live_default_head_icon.webp
│ ├── ic_live_default_icon.png
│ └── ic_live_default_icon2.png
│ ├── values-night
│ └── themes.xml
│ ├── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── themes.xml
│ └── xml
│ ├── backup_rules.xml
│ ├── data_extraction_rules.xml
│ ├── file_provider_paths.xml
│ └── network_security_config.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lib_livepusher
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── duqian
│ └── live
│ └── pusher
│ ├── AgoraPusherImpl.kt
│ ├── IBasePusher.kt
│ ├── ILiveCallback.kt
│ └── helper
│ ├── CommonState.kt
│ └── PusherConfigHelper.kt
├── lib_mediaplayer
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── duqian
│ └── live
│ └── mediaplayer
│ ├── ILivePlayerService.kt
│ ├── PlayState.kt
│ ├── RtmpLivePlayer.kt
│ └── SimpleLivePlayer.kt
├── nativelib
├── .gitignore
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── cpp
│ ├── CMakeLists.txt
│ └── nativelib.cpp
│ └── java
│ └── com
│ └── duqian
│ └── nativelib
│ └── NativeLib.kt
├── screenshot
├── DQLive.mp4
├── DQLive_Anchor.jpeg
├── DQLive_Landscape.png
├── DQLive_Main.png
├── DQLive_Room.jpeg
└── DQLive_Splash.png
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # ---> Android
2 | *.iml
3 | .gradle
4 | /local.properties
5 | .idea/
6 | /.idea/caches
7 | /.idea/libraries
8 | /.idea/modules.xml
9 | /.idea/workspace.xml
10 | /.idea/navEditor.xml
11 | /.idea/assetWizardSettings.xml
12 | .DS_Store
13 | /build
14 | /captures
15 | .externalNativeBuild
16 | .cxx
17 |
18 | # Built application files
19 | *.apk
20 | *.ap_
21 |
22 | # Files for the Dalvik VM
23 | *.dex
24 |
25 | # Java class files
26 | *.class
27 |
28 | # Generated files
29 | bin/
30 | gen/
31 |
32 | # Gradle files
33 | .gradle/
34 | build/
35 |
36 | # Local configuration file (sdk path, etc)
37 | local.properties
38 |
39 | # Proguard folder generated by Eclipse
40 | proguard/
41 |
42 | # Log Files
43 | *.log
44 |
45 | # Android Studio Navigation editor temp files
46 | .navigation/
47 |
48 | # Android Studio captures folder
49 | captures/
50 |
51 | conf*.json
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### DQLive
2 |
3 | #### Android直播客户项目
4 |
5 | #### 直播应用实战!欢迎Star,持续更新。
6 |
7 |
8 | 基于声网SDK实现的推拉流,项目虽小,五脏俱全。
9 | 特色:优良的直播间的架构设计,观众端直播间和主播端,模块复用、可扩展性强,自定义生命周期感知的controller组件,实现按钮粒度的逻辑分离、自由组合。
10 |
11 | Github项目地址:[https://github.com/duqian291902259/DQLive](https://github.com/duqian291902259/DQLive)
12 |
13 | 仅供学习交流技术,应用的sdk、资源和图片素材,如有侵权请联系删除。
14 |
15 | ### 项目截图
16 | 
17 |
18 | 视频效果:[DQLive.mp4](https://github.com/duqian291902259/DQLive/blob/main/screenshot/DQLive.mp4)
19 |
20 | ### 直播技术选型
21 |
22 | 1. 主播端:推流基于声网SDK,然后旁路推流到CDN,所以独特的地方是开播的流程和主播端的音视频处理。其他常规的功能跟观众端类似。
23 | 1. 观众端:拉流播放,可选的播放器有ijkplayer,exo,或者声网MPK;其他主播资料卡,IM功能,打赏功能。
24 | 1. UI架构:Activity+Fragment+ViewModle+LiveData
25 | 1. 自定义的controller(支持热插拔的模块化,生命周期感知)
26 | 1. 组件通信:目前EventBus,会新增LiveDataBus。
27 | 1. 模块化、组件化:AutoService。
28 | 1. 依赖注入:Hilt。
29 | 1. 网络相关:Retrofit + Kotlin Coroutine + Gson。
30 | 1. 持久化:Room,MMKV。
31 | 1. 动画:前期SVGA,后期会+Lottie、Alpha MP4。
32 | 1. 直播使用哪种技术,需要结合各自项目的技术特点,考虑项目同学的开发习惯和技术掌握程度。我也会逐步引入一些项目中没有使用过的,但是很Nice的技术。
33 |
34 | ### 注意事项
35 |
36 | * 直播间的层级,很重要!
37 |
38 | * 直播间的功能可以很多,设计、新增每一个模块,都必须考虑的尽可能兼顾高可用、稳定性、可拓展性、可复用性。
39 |
40 | * 基础组件下沉,做到面向接口编程,尽量模块化、组件化。
41 |
42 | * 方便项目功能模块热插拔,新项目快速复用或者删减。
43 |
44 | * 目前直播间涉及的Modules,很多在直播间和主播端复用。
45 |
46 | * 推拉流的模块LivePusher+MediaPlayer,都是做成单独的module工程,可以打aar,对外提供Service。
47 |
48 | ### 开发的原则和规范
49 |
50 | 规范,可以避免很多没有必要发生的问题,细节不一定决定成败,但可能会影响效率和心情。
51 |
52 | #### 命名规范
53 |
54 | * 各种文件、资源文件命名规范,能方便的检索到想要的内容,也方便以后模块化分离或者重构。
55 |
56 | * 比如图片,一般前缀ic_ ,bg_或者img_. +大模块名+功能名+状态(如果有)
57 | * 。比如ic_live_room_chat_normal.png,ic_push_room_gift_selected.png,大图bg_live_room_loading_default.png。
58 |
59 | * 通用的资源,cm_
60 | * 布局常规的就还按照常规的来:activity_,fragment_,dialog_,layout_,item_
61 |
62 | * 加上模块名+功能名,表意明确。
63 |
64 | * 一般做过插件化的话,资源名太普通或者太短都比较容易冲突。
65 |
66 | * 而规范的命名,做组件化、模块化的时候也比较容易分离资源。
67 |
68 | * 一般room表示直播间和开播端通用资源,pushroom才是开播端独有的。
69 |
70 |
71 | #### 开发约定
72 |
73 | 1. 直播间是一个单Activity的复杂业务载体,附加在里面的功能,一般都要继承直播间的基类开发,方便统一管理。
74 |
75 | 1. 属于直播间内部的新创建的弹窗,满屏或者半屏幕页面,不要使用Activity,继承BaseFragment,或者BaseModule开发。
76 |
77 | 1. 弹窗推荐使用BaseDataBindingDialog(DialogFragment),这样的话,方便子模块的统一管理,也不会因为activity的生命周期变化,影响了直播间的推拉流的逻辑。
78 |
79 | 1. 大的UI,请使用ViewStub懒加载布局。
80 |
81 | ### 其他
82 | 推拉流是正常的,只需要修改推拉流地址即可测试。
83 |
84 | 其实还有很多可以加入的功能,如AI主播,换脸等。后续持续更新。
85 |
86 | duqian2010@gmail.com
87 |
--------------------------------------------------------------------------------
/app_live/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | build/
3 | .idea/
--------------------------------------------------------------------------------
/app_live/README.md:
--------------------------------------------------------------------------------
1 | ### DQLive
2 |
3 | 基于声网SDK实现的推拉流,项目虽小,五脏俱全。
4 | 特色:优秀的直播间的架构设计,观众端直播间和主播端,模块复用、可扩展性强,自定义生命周期感知的controller组件,实现按钮粒度的逻辑分离、自由组合。
5 |
6 | ### 直播技术选型
7 |
8 | 主播端:推流基于声网SDK,然后旁路推流到CDN,所以独特的地方是开播的流程和主播端的音视频处理。其他常规的功能跟观众端类似。
9 | 观众端:拉流播放,可选的播放器有ijkplayer,exo,或者声网MPK;其他主播资料卡,IM功能,打赏功能。
10 | UI架构:Activity+Fragment+ViewModle+LiveData
11 | 自定义的controller(支持热插拔的模块化,生命周期感知)
12 | 组件通信:目前EventBus,会新增LiveDataBus。
13 | 模块化、组件化:AutoService。
14 | 依赖注入:Hilt。
15 | 网络相关:Retrofit + Kotlin Coroutine + Gson。
16 | 持久化:Room,MMKV。
17 | 动画:前期SVGA,后期会+Lottie、Alpha MP4。
18 | 直播使用哪种技术,需要结合各自项目的技术特点,考虑项目同学的开发习惯和技术掌握程度。我也会逐步引入一些项目中没有使用过的,但是很Nice的技术。
19 |
20 | ### 注意事项
21 |
22 | 直播间的层级,很重要!一般需要谨慎设计,不要随意改动,目前设计的符合大部分直播场景下的View层级处理。
23 | 房间内的数据如果要暴露给外部使用,可以用Application级别的GlobalViewModel。否则使用RoomGlobalViewModel。
24 | 直播间的功能可以很多,单个模块也可以做的很复杂很庞大(如礼物特效,视频播放器),所以设计、新增每一个模块,都必须考虑的尽可能兼顾高可用、稳定性、可拓展性、可复用性。
25 | 基础组件下沉,做到面向接口编程,尽量模块化、组件化,方便项目功能模块热插拔,新项目快速复用或者删减。 目前直播间涉及的Modules,很多在直播间和主播端复用。
26 | 推拉流的模块LivePusher+MediaPlayer,都是做成单独的module工程,可以打aar,对外提供Service。
27 |
28 | ### 开发的原则和规范
29 |
30 | 规范,可以避免很多没有必要发生的问题,细节不一定决定成败,但可能会影响效率和心情。
31 |
32 | #### 命名规范
33 |
34 | 各种文件、资源文件命名规范,能方便的检索到想要的内容,也方便以后模块化分离或者重构。 比如图片,一般前缀ic_ ,bg_或者img_. +大模块名+功能名+状态(如果有)
35 | 。比如ic_live_room_chat_normal.png,ic_push_room_gift_selected.png,大图bg_live_room_loading_default.png。
36 | 通用的资源,cm_
37 | activity_,fragment_,dialog_,layout_,item_
38 | ,这些布局常规的就还按照常规的来,加上模块名+功能名。要表意明确。一般做过插件化的话,资源名太普通或者太短都比较容易冲突。
39 | 而规范的命名,做组件化、模块化的时候也比较容易分离资源。如下图所示:一般room表示直播间和开播端通用资源,pushroom才是开播端独有的。
40 | image.png image.png
41 |
42 | #### 开发约定
43 |
44 | 直播间是一个单Activity的复杂业务载体,附加在里面的功能,一般都要继承直播间的基类开发,方便统一管理,享受通用逻辑的封装带来的丝滑开发体验。
45 | 属于直播间内部的新创建的弹窗,满屏或者半屏幕页面,不要使用Activity,继承BaseFragment,或者BaseModule开发,
46 | 弹窗推荐使用BaseDataBindingDialog(DialogFragment),这样的话,方便子模块的统一管理,也不会因为activity的生命周期变化,影响了直播间的推拉流的逻辑。
47 | 大的UI,请使用ViewStub懒加载布局。
48 |
49 | ### 其他
50 | 待更新维护。
51 |
52 | duqian2010@gmail.com
53 | Wechat:AndroidDQ,在路上
54 |
--------------------------------------------------------------------------------
/app_live/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.jetbrains.kotlin.android'
3 | id 'kotlin-android'
4 | id 'kotlin-android-extensions'
5 | id 'kotlin-kapt'
6 | id 'dagger.hilt.android.plugin'
7 | }
8 | if (isLiveModuleLib.toBoolean()) {
9 | apply plugin: 'com.android.library'
10 | } else {
11 | apply plugin: 'com.android.application'
12 | }
13 |
14 | ext {
15 | kotlin_version = '1.4.31'
16 | androidx_appcompat = '1.3.1'
17 | androidx_constraintlayout = '2.1.1'
18 | }
19 |
20 | android {
21 | compileSdk 32
22 |
23 | defaultConfig {
24 | applicationId "com.duqian.app.live"
25 | minSdk 23
26 | targetSdk 31
27 | versionCode 1
28 | versionName "1.0"
29 | }
30 |
31 | buildTypes {
32 | release {
33 | minifyEnabled false
34 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
35 | }
36 | }
37 | compileOptions {
38 | sourceCompatibility JavaVersion.VERSION_1_8
39 | targetCompatibility JavaVersion.VERSION_1_8
40 | }
41 | kotlinOptions {
42 | jvmTarget = '1.8'
43 | }
44 | buildFeatures {
45 | viewBinding true
46 | }
47 |
48 | dataBinding {
49 | enabled = true
50 | }
51 | }
52 |
53 | dependencies {
54 |
55 | // androidx
56 | implementation 'androidx.core:core-ktx:1.6.0'
57 | implementation 'androidx.appcompat:appcompat:1.5.0'
58 | implementation 'com.google.android.material:material:1.6.1'
59 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
60 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
61 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
62 | //implementation 'androidx.lifecycle:lifecycle-common-java8:2.5.1'
63 | implementation 'androidx.recyclerview:recyclerview:1.2.1'
64 |
65 | implementation project(':lib_mediaplayer')
66 | implementation project(':lib_livepusher')
67 | implementation project(path: ':nativelib')
68 | //autoService
69 | kapt 'com.google.auto.service:auto-service:1.0-rc6'
70 | implementation 'com.google.auto.service:auto-service:1.0-rc6'
71 | //mmkv
72 | implementation 'com.tencent:mmkv:1.2.14'
73 | //hilt
74 | implementation "com.google.dagger:hilt-android:2.40"
75 | kapt "com.google.dagger:hilt-android-compiler:2.40"
76 | //cameraX
77 | implementation "androidx.camera:camera-lifecycle:1.2.0-alpha04"
78 | implementation "androidx.camera:camera-view:1.2.0-alpha04"
79 | implementation "androidx.camera:camera-camera2:1.2.0-alpha04"
80 | // Room
81 | implementation "androidx.room:room-runtime:2.3.0"
82 | kapt "androidx.room:room-compiler:2.3.0"
83 | //event bus
84 | implementation 'org.greenrobot:eventbus:3.2.0'
85 |
86 | //statusBar
87 | implementation 'com.gyf.immersionbar:immersionbar:3.0.0'
88 | implementation 'com.gyf.immersionbar:immersionbar-components:3.0.0'
89 | implementation 'com.gyf.immersionbar:immersionbar-ktx:3.0.0'
90 |
91 | // 替换字体
92 | implementation 'io.github.inflationx:calligraphy3:3.1.1'
93 | implementation 'io.github.inflationx:viewpump:2.0.3'
94 |
95 | //easyPermissions
96 | implementation 'pub.devrel:easypermissions:3.0.0'
97 |
98 | //UETool
99 | debugImplementation 'com.github.eleme.UETool:uetool:1.3.4'
100 | debugImplementation 'com.github.eleme.UETool:uetool-base:1.3.4'
101 | releaseImplementation 'com.github.eleme.UETool:uetool-no-op:1.3.4'
102 |
103 | //LiveEventBus
104 | //implementation 'io.github.jeremyliao:live-event-bus-x:1.6.0'
105 |
106 | //recyclerView相关库
107 | //implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46'
108 | //implementation 'com.google.code.gson:gson:2.9.0'
109 |
110 | //glide
111 | //api 'com.github.bumptech.glide:glide:4.13.1'
112 | //annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
113 | //日志
114 | //api 'com.elvishew:xlog:1.6.1'
115 | }
--------------------------------------------------------------------------------
/app_live/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
--------------------------------------------------------------------------------
/app_live/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
42 |
43 |
51 |
52 |
53 |
61 |
62 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app
2 |
3 | import android.Manifest
4 | import android.os.Bundle
5 | import android.util.Log
6 | import android.view.Window
7 | import android.view.WindowManager
8 | import com.duqian.app.base.BaseActivity
9 | import com.duqian.app.live.R
10 | import com.duqian.app.helper.CommonUtils
11 | import com.duqian.app.helper.ToastUtils
12 | import com.duqian.app.live.base_comm.RoomConstants
13 | import com.duqian.app.main.MainFragment
14 | import com.duqian.app.navigator.AppNavigator
15 | import com.duqian.nativelib.NativeLib
16 | import dagger.hilt.android.AndroidEntryPoint
17 | import pub.devrel.easypermissions.EasyPermissions
18 | import javax.inject.Inject
19 |
20 | /**
21 | * Description:首页
22 | *
23 | * Created by 杜乾 on 2022/8/08 - 07:02.
24 | * E-mail: duqian2010@gmail.com
25 | */
26 | @AndroidEntryPoint
27 | class MainActivity : BaseActivity(), EasyPermissions.PermissionCallbacks,
28 | EasyPermissions.RationaleCallbacks {
29 |
30 | @Inject
31 | lateinit var navigator: AppNavigator
32 |
33 | lateinit var mainFragment: MainFragment
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | this.requestWindowFeature(Window.FEATURE_NO_TITLE)
36 | this.window.setFlags(
37 | WindowManager.LayoutParams.FLAG_FULLSCREEN,
38 | WindowManager.LayoutParams.FLAG_FULLSCREEN
39 | )
40 | super.onCreate(savedInstanceState)
41 | setContentView(R.layout.activity_main)
42 | }
43 |
44 | private fun requirePermission() {
45 | if (!CommonUtils.checkSelfPermission(
46 | this,
47 | Manifest.permission.READ_EXTERNAL_STORAGE,
48 | Manifest.permission.WRITE_EXTERNAL_STORAGE,
49 | )
50 | ) {
51 | EasyPermissions.requestPermissions(
52 | this,
53 | getString(R.string.text_external_storage_permission_rationale),
54 | RoomConstants.REQUEST_CODE_EXTERNAL_STORAGE,
55 | Manifest.permission.READ_EXTERNAL_STORAGE,
56 | Manifest.permission.WRITE_EXTERNAL_STORAGE,
57 | )
58 | }
59 | }
60 |
61 | override fun getLayoutId(): Int {
62 | return R.layout.activity_main
63 | }
64 |
65 | override fun initData() {
66 | initMainFragment()
67 |
68 | requirePermission()
69 |
70 | testNative()
71 | }
72 |
73 | private fun testNative() {
74 | ToastUtils.show("Native:${NativeLib().stringFromJNI()}")
75 | }
76 |
77 | override fun isStatusBarDark(): Boolean {
78 | return false
79 | }
80 |
81 | private fun initMainFragment() {
82 | mainFragment = MainFragment.newInstance()
83 | supportFragmentManager.beginTransaction()
84 | .replace(R.id.container, mainFragment)
85 | .commitNow()
86 | }
87 |
88 | override fun onRequestPermissionsResult(
89 | requestCode: Int,
90 | permissions: Array,
91 | grantResults: IntArray
92 | ) {
93 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
94 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
95 | }
96 |
97 | override fun onPermissionsGranted(requestCode: Int, perms: MutableList) {
98 | if (requestCode == RoomConstants.REQUEST_CODE_CAMERA_AND_AUDIO) {
99 | Log.d(TAG, "Granted camera permission....")
100 | mainFragment.startLiveActivity()
101 | }
102 | }
103 |
104 | override fun onPermissionsDenied(requestCode: Int, perms: MutableList) {
105 | ToastUtils.show("no camera permission")
106 | }
107 |
108 | override fun onRationaleAccepted(requestCode: Int) {
109 | }
110 |
111 | override fun onRationaleDenied(requestCode: Int) {
112 | }
113 |
114 | companion object {
115 | private const val TAG = "dq-main"
116 | }
117 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/base/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.base
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.os.Handler
6 | import android.os.Looper
7 | import android.view.*
8 | import androidx.appcompat.app.AppCompatActivity
9 | import com.duqian.app.helper.EventBusHelper
10 | import com.gyf.immersionbar.ktx.immersionBar
11 | import com.duqian.app.live.utils.LiveEventBus
12 | import io.github.inflationx.viewpump.ViewPumpContextWrapper
13 |
14 | /**
15 | * Description:Activity 基类
16 | * Created by 杜小菜 on 2022/8/9 - 19:42.
17 | * E-mail: duqian2010@gmail.com
18 | */
19 | abstract class BaseActivity : AppCompatActivity() {
20 |
21 | val mHandler = Handler(Looper.getMainLooper())
22 | lateinit var mRootView: View
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | handleUnmarshalling(savedInstanceState)
25 | super.onCreate(savedInstanceState)
26 | val root = getRootView()
27 | when {
28 | root != null -> {
29 | this.mRootView = root
30 | }
31 | else -> {
32 | val layoutId = getLayoutId()
33 | this.mRootView = LayoutInflater.from(this).inflate(layoutId, null)
34 | }
35 | }
36 | setContentView(mRootView)
37 | EventBusHelper.bind(this)
38 | initConfig()
39 | initData()
40 | initView()
41 | initListener()
42 | observerData()
43 | }
44 |
45 | override fun finish() {
46 | super.finish()
47 | EventBusHelper.unbind(this)
48 | mHandler.removeCallbacksAndMessages(null)
49 | //确保去除粘性事件
50 | LiveEventBus.get().clear()
51 | }
52 |
53 | override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
54 | return super.dispatchTouchEvent(ev)
55 | }
56 |
57 | private fun handleUnmarshalling(savedInstanceState: Bundle?) {
58 | try {
59 | savedInstanceState?.classLoader = this.javaClass.classLoader
60 | } catch (t: Throwable) {
61 | t.printStackTrace()
62 | }
63 | }
64 |
65 | open fun initConfig() {
66 | immersionBar {
67 | transparentStatusBar()
68 | statusBarDarkFont(isStatusBarDark())
69 | }
70 | }
71 |
72 | open fun isStatusBarDark(): Boolean {
73 | return true
74 | }
75 |
76 | abstract fun getLayoutId(): Int
77 |
78 | abstract fun initData()
79 |
80 | open fun initView() {}
81 |
82 | open fun initListener() {}
83 |
84 | open fun observerData() {}
85 |
86 | open fun getRootView(): View? {
87 | return null
88 | }
89 |
90 | override fun attachBaseContext(newBase: Context?) {
91 | if (newBase != null) {
92 | super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase))
93 | } else {
94 | super.attachBaseContext(newBase)
95 | }
96 | }
97 |
98 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/base/BaseApplication.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.base
2 |
3 | import android.app.Application
4 | import android.util.Log
5 | import com.duqian.app.helper.UEToolHelper
6 | import com.tencent.mmkv.MMKV
7 | import dagger.hilt.android.HiltAndroidApp
8 | import io.github.inflationx.calligraphy3.CalligraphyConfig
9 | import io.github.inflationx.calligraphy3.CalligraphyInterceptor
10 | import io.github.inflationx.viewpump.ViewPump
11 |
12 | /**
13 | * Description:Application基类
14 | * Created by 杜小菜 on 2022/8/9 - 18:22.
15 | * E-mail: duqian2010@gmail.com
16 | */
17 | @HiltAndroidApp
18 | class BaseApplication : Application() {
19 |
20 | companion object {
21 | lateinit var instance: BaseApplication
22 | }
23 |
24 | var mmkv: MMKV? = null
25 |
26 | override fun onCreate() {
27 | super.onCreate()
28 | instance = this
29 |
30 | //Hilt
31 | //mmkv
32 | val rootDir = MMKV.initialize(this)
33 | mmkv = MMKV.defaultMMKV()
34 | Log.d("dq-app", "mmkv root: $rootDir.mmkv=$mmkv")
35 |
36 | //自定义字体
37 | initFonts()
38 |
39 | //dev工具,debug包使用,release包是空实现
40 | UEToolHelper.initUeTool(this)
41 | }
42 |
43 | private fun initFonts() {
44 | ViewPump.init(
45 | ViewPump.builder()
46 | .addInterceptor(
47 | CalligraphyInterceptor(
48 | CalligraphyConfig.Builder()
49 | .setDefaultFontPath("fonts/font_normal.ttf")
50 | //.setFontAttrId(R.attr.fontPath)
51 | .build()
52 | )
53 | ).build()
54 | )
55 | }
56 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/base/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.base
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.os.Handler
6 | import android.os.Looper
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.annotation.CallSuper
11 | import androidx.fragment.app.Fragment
12 | import androidx.fragment.app.FragmentActivity
13 | import com.duqian.app.helper.EventBusHelper
14 | import com.duqian.app.live.utils.LiveEventBus
15 |
16 | /**
17 | * Description:Fragment基类
18 | * Created by 杜乾 on 2022/8/10 - 15:00.
19 | * E-mail: duqian2010@gmail.com
20 | */
21 | abstract class BaseFragment : Fragment() {
22 |
23 | companion object {
24 | private val TAG = "BaseFragment"
25 | }
26 |
27 | lateinit var rootView: View
28 | var isVisibleToUser: Boolean = false
29 | var mHandler = Handler(Looper.getMainLooper())
30 | open lateinit var mContext: Context
31 | open lateinit var mActivity: FragmentActivity
32 |
33 | override fun onCreate(savedInstanceState: Bundle?) {
34 | super.onCreate(savedInstanceState)
35 | parseParams()
36 | }
37 |
38 | open fun parseParams() {
39 | //解析参数
40 | }
41 |
42 | override fun onCreateView(
43 | inflater: LayoutInflater,
44 | container: ViewGroup?,
45 | savedInstanceState: Bundle?
46 | ): View? {
47 | rootView = inflater.inflate(getLayoutId(), container, false)
48 | return rootView
49 | }
50 |
51 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
52 | mContext = requireContext()
53 | mActivity = requireActivity()
54 | EventBusHelper.bind(this)
55 | initData()
56 | initView()
57 | initListener()
58 | observerData()
59 | initControllers()
60 | }
61 |
62 | /**
63 | * 初始化宿主fragment的所有逻辑之后,开始初始化子模块,非必须实现
64 | */
65 | open fun initControllers() {}
66 |
67 |
68 | override fun onResume() {
69 | super.onResume()
70 | // onResume并不代表fragment可见
71 | if (userVisibleHint && !isHidden) {
72 | onVisible()
73 | }
74 | }
75 |
76 | override fun onPause() {
77 | super.onPause()
78 | if (userVisibleHint && !isHidden) {
79 | onInvisible()
80 | }
81 | }
82 |
83 |
84 | override fun onDestroyView() {
85 | super.onDestroyView()
86 | EventBusHelper.unbind(this)
87 | mHandler.removeCallbacksAndMessages(null)
88 | //确保去除粘性事件
89 | LiveEventBus.get().clear()
90 | }
91 |
92 | abstract fun getLayoutId(): Int
93 |
94 | abstract fun initData()
95 |
96 | abstract fun initView()
97 |
98 | open fun initListener() {}
99 |
100 | open fun observerData() {}
101 |
102 | /**
103 | * 当fragment与viewpager、FragmentPagerAdapter一起使用时,切换页面时会调用此方法
104 | *
105 | * @param isVisibleToUser 是否对用户可见
106 | */
107 | override fun setUserVisibleHint(isVisibleToUser: Boolean) {
108 | val change = isVisibleToUser != userVisibleHint
109 | super.setUserVisibleHint(isVisibleToUser)
110 | if (isResumed && change) {
111 | if (userVisibleHint) {
112 | onVisible()
113 | } else {
114 | onInvisible()
115 | }
116 | }
117 | }
118 |
119 | /**
120 | * 当使用show/hide方法时,会触发此回调
121 | *
122 | * @param hidden fragment是否被隐藏
123 | */
124 | override fun onHiddenChanged(hidden: Boolean) {
125 | super.onHiddenChanged(hidden)
126 | if (hidden) {
127 | onInvisible()
128 | } else {
129 | onVisible()
130 | }
131 | }
132 |
133 | @CallSuper
134 | open fun onVisible() {
135 | isVisibleToUser = true
136 |
137 | }
138 |
139 | @CallSuper
140 | open fun onInvisible() {
141 | isVisibleToUser = false
142 | }
143 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/helper/CommonUtils.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.helper
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import androidx.core.app.ActivityCompat
7 | import androidx.core.content.ContextCompat
8 |
9 | /**
10 | * Description:通用工具方法
11 | *
12 | * Created by 杜乾 on 2022/8/12 - 14:10.
13 | * E-mail: duqian2010@gmail.com
14 | */
15 | object CommonUtils {
16 |
17 | /**
18 | * 检查是否有某个权限
19 | */
20 | fun checkSelfPermission(ctx: Context, vararg permission: String): Boolean {
21 | var isGranted = false
22 | for (per in permission) {
23 | isGranted = isGranted && (ContextCompat.checkSelfPermission(ctx.applicationContext, per)
24 | == PackageManager.PERMISSION_GRANTED)
25 | }
26 | return isGranted
27 | }
28 |
29 | /**
30 | * 动态申请多个权限
31 | */
32 | fun requestPermissions(activity: Activity?, permissions: Array, code: Int) {
33 | ActivityCompat.requestPermissions(activity!!, permissions, code)
34 | }
35 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/helper/EventBusHelper.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.helper
2 |
3 | import com.duqian.app.live.base_comm.BaseEvent
4 | import org.greenrobot.eventbus.EventBus
5 |
6 | @Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
7 | @Retention(AnnotationRetention.RUNTIME)
8 | annotation class RegisterEvent
9 |
10 | object EventBusHelper {
11 | private val eventBus = EventBus.getDefault()
12 |
13 | /**
14 | * 新版,发送事件
15 | */
16 | fun sendEvent(event: BaseEvent) {
17 | eventBus.post(event)
18 | }
19 |
20 | fun notifyStickyEvent(event: BaseEvent) {
21 | eventBus.postSticky(event)
22 | }
23 |
24 | fun bind(obj: Any): EventBusHelper {
25 | val clazz: Class<*> = obj.javaClass
26 | if (clazz.isAnnotationPresent(RegisterEvent::class.java)) {
27 | if (!eventBus.isRegistered(obj)) {
28 | eventBus.register(obj)
29 | }
30 | }
31 | return this
32 | }
33 |
34 | fun unbind(obj: Any): EventBusHelper {
35 | if (eventBus.isRegistered(obj)) {
36 | eventBus.unregister(obj)
37 | }
38 | return this
39 | }
40 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/helper/PageRecordHelper.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.helper
2 |
3 | object PageRecordHelper {
4 |
5 | private val recordList = arrayListOf()
6 |
7 | fun record(pageCode: Int) {
8 | if (pageCode == 0) {
9 | recordList.clear()
10 | }
11 | recordList.add(pageCode)
12 | if (recordList.size > 20) {
13 | recordList.removeAt(0)
14 | }
15 | }
16 |
17 | fun getPageRecord(): String {
18 | return recordList.toString()
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/helper/ToastUtils.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.helper
2 |
3 | import android.widget.Toast
4 | import com.duqian.app.base.BaseApplication
5 | import com.duqian.app.live.utils.UIUtil
6 |
7 | /**
8 | * Description:简易的ToastUtils
9 | *
10 | * Created by 杜乾 on 2022/8/11 - 14:23.
11 | * E-mail: duqian2010@gmail.com
12 | */
13 | object ToastUtils {
14 | fun show(content: String) {
15 | val context = BaseApplication.instance
16 | if (UIUtil.isOnUiThread) {
17 | Toast.makeText(context, content, Toast.LENGTH_SHORT).show()
18 | } else {
19 | UIUtil.runOnUiThread({
20 | Toast.makeText(context, content, Toast.LENGTH_SHORT).show()
21 | })
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/helper/UEToolHelper.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.helper
2 |
3 | import android.app.Application.ActivityLifecycleCallbacks
4 | import android.app.Activity
5 | import android.app.Application
6 | import android.os.Bundle
7 | import android.util.Log
8 | import com.duqian.app.live.BuildConfig
9 | import com.duqian.app.base.BaseApplication
10 | import com.duqian.app.live.utils.dpInt
11 | import me.ele.uetool.UETool
12 |
13 | /**
14 | * Created by 杜小菜 on 2022/7/21 - 20:07.
15 | * E-mail: duqian2010@gmail.com
16 | * Description:初始化UETool,设置监听,给了个设置的开关
17 | */
18 | object UEToolHelper {
19 | fun initUeTool(application: Application) {
20 | //仅供debug包使用,egg调试页面,也给了设置的开关
21 | if (!BuildConfig.DEBUG) return
22 | //UETool.putFilterClass(FilterOutView.class);
23 | //UETool.putAttrsProviderClass(CustomAttribution.class);
24 | Log.d("dq-ue", "initUeTool")
25 | application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
26 | private var visibleActivityCount = 0
27 | private var UEToolDismissY = 80.dpInt //不要挡住返回按钮
28 |
29 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
30 |
31 | override fun onActivityStarted(activity: Activity) {
32 | visibleActivityCount++
33 | val enable: Boolean =
34 | BaseApplication.instance.mmkv?.decodeBool("UEToolEnable", false) ?: false
35 | if (visibleActivityCount == 1 && enable) {
36 | val showUETMenu = UETool.showUETMenu(UEToolDismissY)
37 | Log.d("dq-ue", "showUETMenu $showUETMenu")
38 | }
39 | }
40 |
41 | override fun onActivityResumed(activity: Activity) {}
42 | override fun onActivityPaused(activity: Activity) {}
43 | override fun onActivityStopped(activity: Activity) {
44 | visibleActivityCount--
45 | if (visibleActivityCount == 0) {
46 | UEToolDismissY = UETool.dismissUETMenu()
47 | }
48 | }
49 |
50 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
51 | override fun onActivityDestroyed(activity: Activity) {}
52 | })
53 | }
54 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/base_comm/BaseController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.base_comm
2 |
3 | import android.content.Context
4 | import android.os.Handler
5 | import android.os.Looper
6 | import android.view.View
7 | import androidx.fragment.app.FragmentActivity
8 | import androidx.lifecycle.*
9 | import com.duqian.app.base.BaseActivity
10 | import com.duqian.app.base.BaseFragment
11 | import com.duqian.app.helper.EventBusHelper
12 | import com.duqian.app.live.utils.UIUtil
13 | import com.duqian.app.live.viewmodel.RoomGlobalViewModel
14 |
15 | /**
16 | * Description:controller基类,生命周期感知
17 | * Created by 杜乾 on 2022/8/11 - 11:15.
18 | * E-mail: duqian2010@gmail.com
19 | */
20 | //如果要监听event消息,请在实现类中添加注解: @RegisterEvent
21 | abstract class BaseController(val mRootView: View, val mLifecycleOwner: LifecycleOwner) :
22 | DefaultLifecycleObserver {
23 |
24 | var mHandler = Handler(Looper.getMainLooper())
25 | lateinit var mActivity: FragmentActivity //这个不为空
26 | lateinit var mContext: Context //这个不为空
27 | var mGlobalViewModel: RoomGlobalViewModel? = null
28 |
29 | //注意初始化的时候传入了fragment,才会不为空
30 | var mFragment: BaseFragment? = null
31 |
32 | init {
33 | initBaseContext()
34 | }
35 |
36 | private fun initBaseContext() {
37 | //约束LifecycleOwner必须实现基类BaseFragment或则BaseActivity
38 | if (mLifecycleOwner is BaseFragment) {
39 | this.mFragment = mLifecycleOwner
40 | this.mActivity = mFragment!!.activity as BaseActivity
41 | mContext = mLifecycleOwner.requireContext()
42 | }
43 | if (mLifecycleOwner is BaseActivity) {
44 | this.mActivity = mLifecycleOwner
45 | mContext = mLifecycleOwner
46 | }
47 | val owner = mActivity as ViewModelStoreOwner
48 | mGlobalViewModel = ViewModelProvider(owner)[RoomGlobalViewModel::class.java]
49 | mGlobalViewModel?.isLandscape?.observe(mLifecycleOwner) {
50 | onConfigurationChanged(it)
51 | }
52 | register(mLifecycleOwner)
53 | }
54 |
55 | open fun onConfigurationChanged(isLandscape: Boolean) {
56 |
57 | }
58 |
59 | abstract fun initView(rootView: View)
60 | open fun onResume() {}
61 | open fun onStart() {}
62 | open fun onPause() {}
63 | open fun onStop() {}
64 | abstract fun onDestroy()
65 |
66 | fun isPushRoom(): Boolean {
67 | return mGlobalViewModel?.isPushRoom?.value ?: false
68 | }
69 |
70 | private fun register(owner: LifecycleOwner) {
71 | owner.lifecycle.addObserver(this)
72 | }
73 |
74 | override fun onCreate(owner: LifecycleOwner) {
75 | super.onCreate(owner)
76 | EventBusHelper.bind(this)
77 | initView(mRootView)
78 | observerData()
79 | initData()
80 | }
81 |
82 | open fun observerData() {}
83 |
84 | open fun initData() {}
85 |
86 | override fun onStart(owner: LifecycleOwner) {
87 | super.onStart(owner)
88 | onStart()
89 | }
90 |
91 | override fun onResume(owner: LifecycleOwner) {
92 | super.onResume(owner)
93 | onResume()
94 | }
95 |
96 | override fun onPause(owner: LifecycleOwner) {
97 | super.onPause(owner)
98 | onPause()
99 | }
100 |
101 | override fun onStop(owner: LifecycleOwner) {
102 | super.onStop(owner)
103 | onStop()
104 | }
105 |
106 | override fun onDestroy(owner: LifecycleOwner) {
107 | super.onDestroy(owner)
108 | EventBusHelper.unbind(this)
109 | onDestroy()
110 | mHandler.removeCallbacksAndMessages(null)
111 | mLifecycleOwner.lifecycle.removeObserver(this)
112 | }
113 |
114 | open fun runOnUiThread(action: Runnable) {
115 | if (UIUtil.isOnUiThread) {
116 | action.run()
117 | } else {
118 | mHandler.post(action)
119 | }
120 | }
121 |
122 | open fun runOnUiThreadDelay(action: Runnable, delay: Long) {
123 | mHandler.postDelayed(action, delay)
124 | }
125 |
126 | open fun runOnUiThreadAtFront(action: Runnable) {
127 | mHandler.postAtFrontOfQueue(action)
128 | }
129 |
130 | open fun removeCallbacks(action: Runnable) {
131 | mHandler.removeCallbacks(action)
132 | }
133 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/base_comm/BaseDataBindingDialog.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.base_comm
2 |
3 | import androidx.databinding.ViewDataBinding
4 | import android.view.LayoutInflater
5 | import android.view.ViewGroup
6 | import android.os.Bundle
7 | import android.view.View
8 | import androidx.databinding.DataBindingUtil
9 |
10 | /**
11 | * Description:使用DataBinding的dialog基类
12 | *
13 | * Created by 杜乾 on 2022/8/13 - 22:21.
14 | * E-mail: duqian2010@gmail.com
15 | */
16 | abstract class BaseDataBindingDialog : BaseDialogFragment() {
17 | //暴露给外部调用
18 | open var mDataBinding: V? = null
19 |
20 | override fun onCreateView(
21 | inflater: LayoutInflater,
22 | container: ViewGroup?,
23 | savedInstanceState: Bundle?
24 | ): View? {
25 | mDataBinding = DataBindingUtil.inflate(inflater, layoutId, container, true)
26 | return mDataBinding!!.root
27 | }
28 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/base_comm/BaseDialogFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.base_comm
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.DialogFragment
8 | import pub.devrel.easypermissions.EasyPermissions.PermissionCallbacks
9 | import pub.devrel.easypermissions.EasyPermissions
10 |
11 | /**
12 | * Description:DialogFragment基类
13 | *
14 | * Created by 杜乾 on 2022/8/13 - 22:22.
15 | * E-mail: duqian2010@gmail.com
16 | */
17 | abstract class BaseDialogFragment : DialogFragment() {
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | val bundle = arguments
21 | bundle?.let { parseBundle(it) }
22 | }
23 |
24 | private fun parseBundle(bundle: Bundle) {}
25 | override fun onCreateView(
26 | inflater: LayoutInflater,
27 | container: ViewGroup?,
28 | savedInstanceState: Bundle?
29 | ): View? {
30 | return inflater.inflate(layoutId, container)
31 | }
32 |
33 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
34 | super.onViewCreated(view, savedInstanceState)
35 | iniView()
36 | }
37 |
38 | override fun onActivityCreated(savedInstanceState: Bundle?) {
39 | super.onActivityCreated(savedInstanceState)
40 | iniListener()
41 | iniData()
42 | }
43 |
44 | abstract val layoutId: Int
45 | abstract fun iniView()
46 | abstract fun iniData()
47 | abstract fun iniListener()
48 |
49 | override fun onRequestPermissionsResult(
50 | requestCode: Int, permissions: Array,
51 | grantResults: IntArray
52 | ) {
53 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
54 | if (this is PermissionCallbacks) {
55 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
56 | }
57 | }
58 |
59 | val isShowing: Boolean get() = dialog != null && dialog!!.isShowing
60 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/base_comm/BaseEvent.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.base_comm
2 |
3 | /**
4 | * Description:Event基类,所有EventBus事件,继承这个类
5 | * Created by 杜小菜 on 2022/8/10 - 12:41.
6 | * E-mail: duqian2010@gmail.com
7 | */
8 | open class BaseEvent(val eventCode: Int, val data: Any?) {
9 | //eventCode,int类型的常量,
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/base_comm/BaseLiveActivity.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.base_comm
2 |
3 | import android.content.Intent
4 | import android.util.Log
5 | import android.view.View
6 | import android.view.WindowManager
7 | import androidx.lifecycle.ViewModelProvider
8 | import androidx.lifecycle.observe
9 | import com.duqian.app.live.R
10 | import com.duqian.app.base.BaseActivity
11 | import com.duqian.app.live.liveroom.data.LiveRoomEvent
12 | import com.duqian.app.live.liveroom.data.LiveRoomEventCode
13 | import com.duqian.app.helper.EventBusHelper
14 | import com.duqian.app.live.utils.UIUtil
15 | import com.duqian.app.live.viewmodel.RoomGlobalViewModel
16 | import pub.devrel.easypermissions.EasyPermissions
17 |
18 | /**
19 | * Description:房间专属的Activity基类,方便管理房间的状态和回调一些房间内的事件
20 | * 开播端和观众端共同继承的基类,提供给后续扩展相同等功能逻辑
21 | * Created by 杜乾 on 2022/8/10 - 15:39.
22 | * E-mail: duqian2010@gmail.com
23 | */
24 | abstract class BaseLiveActivity : BaseActivity(), EasyPermissions.PermissionCallbacks {
25 |
26 | var mIsPushRoom = false //activity初始化的时候会设值
27 | var mRoomId = 0
28 |
29 | /**
30 | * 进入房间传递的参数,参数不同时,观众端/主播端按需转换成实现类
31 | */
32 | lateinit var mRoomParams: BaseRoomParams
33 | lateinit var mGlobalViewModel: RoomGlobalViewModel
34 |
35 | override fun initData() {
36 | mRoomParams = intent.getSerializableExtra(ParamKey.PARAM_KEY_ROOM_DATA) as BaseRoomParams
37 | mRoomId = mRoomParams.roomId
38 | mIsPushRoom = mRoomParams.isPushRoom
39 | Log.d("dq-room", "mRoomParams=$mRoomParams")
40 |
41 | //全局的viewModel
42 | mGlobalViewModel = ViewModelProvider(this)[RoomGlobalViewModel::class.java]
43 | mGlobalViewModel.isPushRoom.value = mIsPushRoom
44 | mGlobalViewModel.roomId.value = mRoomId
45 | mGlobalViewModel.roomType.value = RoomType.ROOM_TYPE_SINGLE
46 |
47 | mGlobalViewModel.isLandscape.observe(this) {
48 | onConfigurationChanged(it)
49 | }
50 |
51 | }
52 |
53 | open fun onConfigurationChanged(isLandscape: Boolean) {
54 |
55 | }
56 |
57 | override fun initView() {
58 | super.initView()
59 | window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
60 | initControllers()
61 | }
62 |
63 | open fun initControllers() {
64 | //初始化各自activity的controllers
65 | }
66 |
67 | override fun onNewIntent(intent: Intent?) {
68 | super.onNewIntent(intent)
69 | }
70 |
71 | override fun isStatusBarDark(): Boolean {
72 | return false
73 | }
74 |
75 | override fun initListener() {
76 | super.initListener()
77 | mRootView.findViewById(R.id.iv_close_room)?.setOnClickListener {
78 | closeRoom()
79 | }
80 | }
81 |
82 | override fun onBackPressed() {
83 | super.onBackPressed()
84 | closeRoom()
85 | }
86 |
87 | private fun closeRoom() {
88 | //发送关闭直播间给其他业务逻辑处理下播和释放资源逻辑
89 | if (mIsPushRoom) {
90 | //ToastUtils.show("主播端,弹窗确认再退出直播间")
91 | }
92 | EventBusHelper.sendEvent(
93 | LiveRoomEvent(
94 | LiveRoomEventCode.EVENT_ID_LIVE_ROOM_END,
95 | mIsPushRoom
96 | )
97 | )
98 | finish()
99 | }
100 |
101 | open fun runUiThread(action: Runnable) {
102 | if (UIUtil.isOnUiThread) {
103 | action.run()
104 | } else {
105 | mHandler.post(action)
106 | }
107 | }
108 |
109 | open fun runOnUiThreadDelay(action: Runnable, delay: Long) {
110 | mHandler.postDelayed(action, delay)
111 | }
112 |
113 | open fun runOnUiThreadAtFront(action: Runnable) {
114 | mHandler.postAtFrontOfQueue(action)
115 | }
116 |
117 | open fun removeCallbacks(action: Runnable) {
118 | mHandler.removeCallbacks(action)
119 | }
120 |
121 | override fun onRequestPermissionsResult(
122 | requestCode: Int,
123 | permissions: Array,
124 | grantResults: IntArray
125 | ) {
126 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
127 | // Forward results to EasyPermissions
128 | EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this)
129 | }
130 |
131 | override fun onPermissionsGranted(requestCode: Int, perms: MutableList) {
132 | Log.d("dq-room", "onPermissionsGranted requestCode=$requestCode")
133 | }
134 |
135 | override fun onPermissionsDenied(requestCode: Int, perms: MutableList) {
136 | Log.d("dq-room", "onPermissionsDenied requestCode=$requestCode")
137 | }
138 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/base_comm/BaseLiveFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.base_comm
2 |
3 | import android.text.TextUtils
4 | import androidx.fragment.app.Fragment
5 | import androidx.lifecycle.ViewModelProvider
6 | import androidx.lifecycle.ViewModelStoreOwner
7 | import androidx.lifecycle.observe
8 | import com.duqian.app.base.BaseFragment
9 | import com.duqian.app.live.utils.UIUtil
10 | import com.duqian.app.live.viewmodel.RoomGlobalViewModel
11 |
12 | /**
13 | * Description:房间专属的fragment基类,方便回调一些房间内的事件
14 | * 提供给后续扩展都有的功能逻辑
15 | * Created by 杜乾 on 2022/8/10 - 15:39.
16 | * E-mail: duqian2010@gmail.com
17 | */
18 | abstract class BaseLiveFragment : BaseFragment() {
19 | var mRoomParams: BaseRoomParams? = null
20 | var mRoomId = 0
21 | var mGlobalViewModel: RoomGlobalViewModel? = null
22 |
23 | override fun initListener() {
24 |
25 | }
26 |
27 | override fun observerData() {
28 | val owner = activity as ViewModelStoreOwner
29 | mGlobalViewModel = ViewModelProvider(owner)[RoomGlobalViewModel::class.java]
30 | mGlobalViewModel?.isLandscape?.observe(this) {
31 | onConfigurationChanged(it)
32 | }
33 | }
34 |
35 | open fun onConfigurationChanged(isLandscape: Boolean) {
36 |
37 | }
38 |
39 | override fun parseParams() {
40 | mRoomParams = arguments?.getSerializable(ParamKey.PARAM_KEY_ROOM_DATA) as BaseRoomParams?
41 | mRoomId = mRoomParams?.roomId ?: 0
42 | }
43 |
44 | fun isPushRoom(): Boolean {
45 | return mRoomParams?.isPushRoom == true
46 | }
47 |
48 | open fun runOnUiThread(action: Runnable) {
49 | if (UIUtil.isOnUiThread) {
50 | action.run()
51 | } else {
52 | mHandler.post(action)
53 | }
54 | }
55 |
56 | open fun runOnUiThreadDelay(action: Runnable, delay: Long) {
57 | mHandler.postDelayed(action, delay)
58 | }
59 |
60 | open fun runOnUiThreadAtFront(action: Runnable) {
61 | mHandler.postAtFrontOfQueue(action)
62 | }
63 |
64 | open fun removeCallbacks(action: Runnable) {
65 | mHandler.removeCallbacks(action)
66 | }
67 |
68 | /**
69 | * 房间内只允许增加继承自BaseLiveFragment的fragment
70 | * 方便监听房间内的事件和通用处理逻辑
71 | */
72 | open fun addFragment(contentId: Int, fragment: BaseLiveFragment, tag: String? = "") {
73 | activity?.supportFragmentManager?.beginTransaction()
74 | ?.replace(contentId, fragment, tag)
75 | //.addToBackStack((fragment as Any).javaClass.simpleName)
76 | ?.disallowAddToBackStack()
77 | ?.commitAllowingStateLoss()
78 | }
79 |
80 | open fun removeFragment(fragment: Fragment) {
81 | activity?.supportFragmentManager?.beginTransaction()
82 | ?.remove(fragment)
83 | ?.commitAllowingStateLoss()
84 | }
85 |
86 | open fun removeFragmentByTag(fragmentTag: String? = "") {
87 | if (TextUtils.isEmpty(fragmentTag)) return
88 | val fragmentByTag =
89 | activity?.supportFragmentManager?.findFragmentByTag(fragmentTag) as Fragment
90 | removeFragment(fragmentByTag)
91 | }
92 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/base_comm/BaseRoomParams.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.base_comm
2 |
3 | import java.io.Serializable
4 |
5 | /**
6 | * Description:房间参数基类,外部进入房间,传参用对应的子类做下封装:直播间用RoomParams,开播端用LiveRoomParams
7 | *
8 | * Created by 杜乾 on 2022/8/10 - 18:23.
9 | * E-mail: duqian2010@gmail.com
10 | */
11 | open class BaseRoomParams(val roomId: Int, val from: String? = "") :
12 | Serializable {
13 | //parcelable效率高,但是要实现比较多方法,暂时用Serializable,参数多后用parcelable
14 |
15 | var isPushRoom = false //activity初始化的时候会设值
16 |
17 | var roomType = RoomType.ROOM_TYPE_SINGLE
18 |
19 | var payFrom = ""
20 |
21 | override fun toString(): String {
22 | return "BaseRoomParams(roomId=$roomId, from=$from, isPushRoom=$isPushRoom, payFrom='$payFrom')"
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/base_comm/RoomConstants.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.base_comm
2 |
3 | /**
4 | * Description:房间常量
5 | * Created by 杜乾 on 2022/8/10 - 16:22.
6 | * E-mail: duqian2010@gmail.com
7 | */
8 | //房间常量
9 | object RoomConstants {
10 | const val HOST_STATUS_ON_LIVE = 0
11 | const val HOST_STATUS_LEAVE = 1
12 |
13 | //权限申请
14 | private const val REQUEST_CODE_BASE = 5000
15 | const val REQUEST_CODE_CAMERA_AND_AUDIO = REQUEST_CODE_BASE + 1
16 | const val REQUEST_CODE_EXTERNAL_STORAGE = REQUEST_CODE_BASE + 2
17 | }
18 |
19 | //本地的房间类型
20 | object RoomType {
21 | const val ROOM_TYPE_SINGLE = 0 //目前只有单人直播的直播间
22 | const val ROOM_TYPE_DOUBLE = 2 //双人连麦的直播间
23 | }
24 |
25 | //from,进入直播间的来源
26 | object RoomFrom {
27 | const val ROOM_FROM_LIVE = "from_live" //从主页live列表进入直播间
28 | }
29 |
30 | //传参数的Key
31 | object ParamKey {
32 | const val PARAM_KEY_ROOM_DATA = "room_data"
33 | const val PARAM_KEY_DIALOG_DATA = "dialog_data"
34 | }
35 |
36 | //LiveEventBus的Key
37 | object LiveEventKey {
38 | const val EVENT_KEY_VIEWER_COUNT = "viewer_count" //房间人数变化
39 | const val EVENT_KEY_CLEAR_LIVE_SCREEN = "clear_live_screen" //清屏
40 | const val EVENT_KEY_SEND_GIFT_START = "send_gift_start" //开始送礼
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/controller/AnchorInfoController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.controller
2 |
3 | import android.util.Log
4 | import android.view.View
5 | import android.widget.ImageView
6 | import android.widget.TextView
7 | import androidx.lifecycle.LifecycleOwner
8 | import com.duqian.app.live.R
9 | import com.duqian.app.helper.RegisterEvent
10 | import com.duqian.app.helper.ToastUtils
11 | import com.duqian.app.live.utils.setViewVisible
12 | import com.duqian.app.live.base_comm.BaseController
13 | import com.duqian.app.live.base_comm.LiveEventKey
14 | import com.duqian.app.live.liveroom.data.LiveRoomEvent
15 | import com.duqian.app.live.liveroom.data.LiveRoomEventCode
16 | import com.duqian.app.live.utils.LiveEventBus
17 | import org.greenrobot.eventbus.Subscribe
18 | import org.greenrobot.eventbus.ThreadMode
19 |
20 | /**
21 | * Description:主播头像,昵称,观众人数,follow等逻辑,目前直播间/开播端目前公用
22 | *
23 | * Created by 杜乾 on 2022/8/11 - 13:38.
24 | * E-mail: duqian2010@gmail.com
25 | */
26 | @RegisterEvent
27 | class AnchorInfoController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
28 |
29 | companion object {
30 | private const val TAG = "AnchorInfoController"
31 | }
32 |
33 | //获取view的方式,也可以搞成dataBinding,参看RoomCommonController
34 | override fun initView(rootView: View) {
35 | val tvViewerCount = rootView.findViewById(R.id.tvOnlineNum)
36 | //房间人数更新监听
37 | LiveEventBus.get().with(LiveEventKey.EVENT_KEY_VIEWER_COUNT, Int::class.java)
38 | .observe(mLifecycleOwner, true) {
39 | tvViewerCount.text = "$it"
40 | Log.d(TAG, "dq onCreate count = $it,activity=$mActivity fragment=$mFragment")
41 | }
42 |
43 | val followIcon = rootView.findViewById(R.id.ivAnchorFollow)
44 | followIcon.setViewVisible(!isPushRoom())
45 | followIcon.setOnClickListener {
46 | ToastUtils.show("Followed")
47 | followIcon.setViewVisible(false)
48 | }
49 | }
50 |
51 | override fun initData() {
52 | super.initData()
53 | }
54 |
55 | override fun onResume() {
56 | Log.d(TAG, "dq onResume")
57 | }
58 |
59 | @Subscribe(threadMode = ThreadMode.MAIN)
60 | fun onRoomEvent(event: LiveRoomEvent) {
61 | when (event.eventCode) {
62 | LiveRoomEventCode.EVENT_ID_LIVE_ROOM_END -> {
63 | }
64 | }
65 | }
66 |
67 |
68 | override fun onDestroy() {
69 | Log.d(TAG, "dq onDestroy")
70 | }
71 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/controller/RoomChatBtnController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.controller
2 |
3 | import android.view.View
4 | import android.widget.ImageView
5 | import androidx.lifecycle.LifecycleOwner
6 | import com.duqian.app.live.R
7 | import com.duqian.app.helper.RegisterEvent
8 | import com.duqian.app.helper.ToastUtils
9 | import com.duqian.app.live.base_comm.BaseController
10 | import com.duqian.app.live.liveroom.data.LiveRoomEvent
11 | import org.greenrobot.eventbus.Subscribe
12 | import org.greenrobot.eventbus.ThreadMode
13 |
14 | /**
15 | * Description:直播间底部,聊天按钮的处理逻辑
16 | *
17 | * Created by 杜乾 on 2022/8/11 - 19:43.
18 | * E-mail: duqian2010@gmail.com
19 | */
20 | @RegisterEvent
21 | class RoomChatBtnController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
22 |
23 | override fun initView(rootView: View) {
24 | rootView.findViewById(R.id.ivRoomChat)?.setOnClickListener {
25 | ToastUtils.show("弹起面板,发送聊天消息,isPushRoom=${isPushRoom()}")
26 | }
27 | }
28 |
29 | override fun onResume() {
30 | }
31 |
32 | @Subscribe(threadMode = ThreadMode.MAIN)
33 | fun onRoomEvent(event: LiveRoomEvent) {
34 | when (event.eventCode) {
35 |
36 | }
37 | }
38 |
39 | override fun onDestroy() {
40 | }
41 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/controller/RoomCommonController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.controller
2 |
3 | import android.view.View
4 | import android.view.ViewGroup
5 | import android.widget.TextView
6 | import androidx.constraintlayout.widget.ConstraintLayout
7 | import androidx.databinding.DataBindingUtil
8 | import androidx.lifecycle.LifecycleOwner
9 | import com.duqian.app.live.R
10 | import com.duqian.app.live.databinding.IncludeRoomCommonLayoutBinding
11 | import com.duqian.app.live.base_comm.BaseController
12 | import com.duqian.app.live.base_comm.LiveEventKey
13 | import com.duqian.app.live.base_comm.RoomType
14 | import com.duqian.app.live.utils.LiveEventBus
15 | import com.duqian.app.live.utils.dp
16 | import com.duqian.app.live.utils.setViewVisible
17 |
18 | /**
19 | * Description:直播间一些通用的处理逻辑
20 | *
21 | * Created by 杜乾 on 2022/8/11 - 19:43.
22 | * E-mail: duqian2010@gmail.com
23 | */
24 | class RoomCommonController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
25 |
26 | override fun initView(rootView: View) {
27 | val binding: IncludeRoomCommonLayoutBinding = DataBindingUtil.inflate(
28 | mActivity.layoutInflater,
29 | R.layout.include_room_common_layout,
30 | rootView as ViewGroup,
31 | true
32 | )
33 |
34 | //databinding层级最高的问题,父布局是Include的话,会直接加载直播间布局的最上层
35 | binding.tvRoomCommonTips.setViewVisible(!isPushRoom())
36 | binding.tvRoomCommonTips.text = "点击模拟切换直播间"
37 | binding.tvRoomCommonTips.setOnClickListener {
38 | mGlobalViewModel?.roomType?.value = RoomType.ROOM_TYPE_DOUBLE
39 | }
40 |
41 | if (isPushRoom()) {
42 | binding.tvLive.text = "主播端"
43 | binding.tvLive.setTextColor(mContext.resources.getColor(R.color.white, null))
44 | } else {
45 | binding.tvLive.text = "直播间"
46 | }
47 |
48 | binding.tvLive.setOnClickListener {
49 | LiveEventBus.get().with(LiveEventKey.EVENT_KEY_VIEWER_COUNT, Int::class.java)
50 | .postValue((Math.random() * 1000).toInt() + 1999)
51 | }
52 | }
53 |
54 | override fun onConfigurationChanged(isLandscape: Boolean) {
55 | val tvLive = mRootView.findViewById(R.id.tvLive)
56 | if (!isPushRoom()) {
57 | tvLive.text = if (isLandscape) "横屏直播间" else "直播间.."
58 | } else {
59 | tvLive.text = "主播端"
60 | }
61 | val layoutParams = tvLive.layoutParams as ConstraintLayout.LayoutParams
62 | layoutParams.topMargin = 100.dp
63 | tvLive.layoutParams = layoutParams
64 | tvLive.setOnClickListener {
65 | LiveEventBus.get().with(LiveEventKey.EVENT_KEY_VIEWER_COUNT, Int::class.java)
66 | .postValue((Math.random() * 1000).toInt() + 1999)
67 | }
68 | }
69 |
70 | override fun onResume() {
71 |
72 | }
73 |
74 | override fun onDestroy() {
75 | }
76 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/controller/RoomGiftBtnController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.controller
2 |
3 | import android.view.View
4 | import android.widget.ImageView
5 | import androidx.lifecycle.LifecycleOwner
6 | import com.duqian.app.live.R
7 | import com.duqian.app.live.base_comm.BaseController
8 | import com.duqian.app.live.base_comm.LiveEventKey
9 | import com.duqian.app.live.fragment.dialog.RoomGiftDialog
10 | import com.duqian.app.live.services.AutoServiceHelper
11 | import com.duqian.app.live.utils.FragmentUtils
12 | import com.duqian.app.live.utils.LiveEventBus
13 | import com.duqian.live.mediaplayer.ILivePlayerService
14 |
15 | /**
16 | * Description:直播间底部,礼物按钮的处理逻辑
17 | *
18 | * Created by 杜乾 on 2022/8/11 - 19:43.
19 | * E-mail: duqian2010@gmail.com
20 | */
21 | class RoomGiftBtnController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
22 |
23 | override fun initView(rootView: View) {
24 | rootView.findViewById(R.id.ivRoomGift).setOnClickListener {
25 |
26 | val dialogFragment = RoomGiftDialog.newInstance()
27 | dialogFragment.callback = object : RoomGiftDialog.DialogCallback {
28 | override fun onClicked() {
29 | testGiftEffect()
30 | }
31 | }
32 | FragmentUtils.show(mActivity, dialogFragment)
33 | }
34 | }
35 |
36 | private fun testGiftEffect() {
37 | LiveEventBus.get().with(LiveEventKey.EVENT_KEY_SEND_GIFT_START, Boolean::class.java)
38 | .postValue(true)
39 | FragmentUtils.dismissAllowingStateLoss(
40 | mActivity.supportFragmentManager,
41 | RoomGiftDialog::class.java.simpleName
42 | )
43 | }
44 |
45 | override fun onResume() {
46 | }
47 |
48 | override fun onDestroy() {
49 | val playerService = AutoServiceHelper.load(ILivePlayerService::class.java)
50 | playerService?.stopPlay()
51 | }
52 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/fragment/PublicChatFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.fragment
2 |
3 | import android.os.Bundle
4 | import com.duqian.app.live.R
5 | import com.duqian.app.live.utils.setViewVisible
6 | import com.duqian.app.live.base_comm.BaseLiveFragment
7 | import com.duqian.app.live.base_comm.BaseRoomParams
8 | import com.duqian.app.live.base_comm.ParamKey
9 | import kotlinx.android.synthetic.main.fragment_room_public_chat.*
10 |
11 | /**
12 | * Description:直播间 公屏fragment
13 | *
14 | * Created by 杜乾 on 2022/8/11 - 19:20.
15 | * E-mail: duqian2010@gmail.com
16 | */
17 | class PublicChatFragment : BaseLiveFragment() {
18 |
19 | //controller或者子fragment实现
20 |
21 | override fun getLayoutId(): Int {
22 | return R.layout.fragment_room_public_chat
23 | }
24 |
25 | override fun initControllers() {
26 |
27 | }
28 |
29 | override fun initData() {
30 |
31 | }
32 |
33 | override fun initView() {
34 |
35 | }
36 |
37 | override fun observerData() {
38 | super.observerData()
39 | mGlobalViewModel?.isLandscape?.observe(this) {
40 | rootPublicChat.setViewVisible(!it)
41 | }
42 | }
43 |
44 | companion object {
45 | /**
46 | * 传入的参数,后续按需扩展,进入房间的参数放BaseRoomParams,其他额外的参数视情况添加
47 | */
48 | fun newInstance(data: BaseRoomParams? = null): PublicChatFragment {
49 | val fragment = PublicChatFragment()
50 | val bundle = Bundle()
51 | bundle.putSerializable(ParamKey.PARAM_KEY_ROOM_DATA, data)
52 | fragment.arguments = bundle
53 | return fragment
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/fragment/RoomBottomBtnFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.fragment
2 |
3 | import android.os.Bundle
4 | import com.duqian.app.live.R
5 | import com.duqian.app.live.liveroom.controller.RoomCallBtnController
6 | import com.duqian.app.live.liveroom.controller.RoomCoinsBtnController
7 | import com.duqian.app.live.pushroom.controller.PushMicBtnController
8 | import com.duqian.app.live.base_comm.BaseLiveFragment
9 | import com.duqian.app.live.base_comm.BaseRoomParams
10 | import com.duqian.app.live.base_comm.ParamKey
11 | import com.duqian.app.live.controller.RoomChatBtnController
12 | import com.duqian.app.live.controller.RoomGiftBtnController
13 |
14 | /**
15 | * Description:直播间底部按钮fragment
16 | *
17 | * Created by 杜乾 on 2022/8/11 - 19:20.
18 | * E-mail: duqian2010@gmail.com
19 | */
20 | class RoomBottomBtnFragment : BaseLiveFragment() {
21 |
22 | //聊天按钮对应的逻辑
23 | private lateinit var mChatBtnController: RoomChatBtnController
24 | //送礼
25 | private lateinit var mGiftBtnController: RoomGiftBtnController
26 | //充值
27 | private lateinit var mCoinsBtnController: RoomCoinsBtnController
28 | //打电话
29 | private lateinit var mCallBtnController: RoomCallBtnController
30 | //麦克风
31 | private lateinit var mMicVoiceBtnController: PushMicBtnController
32 |
33 | override fun getLayoutId(): Int {
34 | if (isPushRoom()) {
35 | return R.layout.fragment_pushroom_bottom_btns
36 | }
37 | return R.layout.fragment_liveroom_bottom_btns
38 | }
39 |
40 | override fun initControllers() {
41 | //通用的按钮
42 | mChatBtnController = RoomChatBtnController(rootView, this)
43 | mGiftBtnController = RoomGiftBtnController(rootView, this)
44 |
45 | if (isPushRoom()) {
46 | //主播端初始化底部按钮
47 | mMicVoiceBtnController = PushMicBtnController(rootView.findViewById(R.id.ivRoomMicVoice), this)
48 | } else {
49 | //用户端才需要初始化的按钮逻辑
50 | mCoinsBtnController = RoomCoinsBtnController(rootView, this)
51 | mCallBtnController = RoomCallBtnController(rootView, this)
52 | }
53 | }
54 |
55 | override fun initData() {
56 |
57 | }
58 |
59 | override fun initView() {
60 |
61 | }
62 |
63 | companion object {
64 | private const val TAG = "LiveRoomBottomFragment"
65 |
66 | /**
67 | * 传入的参数,后续按需扩展,进入房间的参数放BaseRoomParams,其他额外的参数视情况添加
68 | */
69 | fun newInstance(data: BaseRoomParams? = null): RoomBottomBtnFragment {
70 | val fragment = RoomBottomBtnFragment()
71 | val bundle = Bundle()
72 | bundle.putSerializable(ParamKey.PARAM_KEY_ROOM_DATA, data)
73 | fragment.arguments = bundle
74 | return fragment
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/fragment/RoomEffectFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.fragment
2 |
3 | import android.os.Bundle
4 | import com.duqian.app.live.R
5 | import com.duqian.app.live.base_comm.BaseLiveFragment
6 | import com.duqian.app.live.base_comm.BaseRoomParams
7 | import com.duqian.app.live.base_comm.ParamKey
8 |
9 | /**
10 | * Description:Description:直播间 特效 fragment
11 | *
12 | * Created by 杜乾 on 2022/8/12 - 14:23.
13 | * E-mail: duqian2010@gmail.com
14 | */
15 | class RoomEffectFragment : BaseLiveFragment() {
16 |
17 | override fun getLayoutId(): Int {
18 | return R.layout.fragment_room_gift_effect
19 | }
20 |
21 | override fun initData() {
22 |
23 | }
24 |
25 | override fun initView() {
26 |
27 | }
28 |
29 | companion object {
30 | /**
31 | * 传入的参数,后续按需扩展,进入房间的参数放BaseRoomParams,其他额外的参数视情况添加
32 | */
33 | fun newInstance(data: BaseRoomParams? = null): RoomEffectFragment {
34 | val fragment = RoomEffectFragment()
35 | val bundle = Bundle()
36 | bundle.putSerializable(ParamKey.PARAM_KEY_ROOM_DATA, data)
37 | fragment.arguments = bundle
38 | return fragment
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/fragment/RoomMainFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.fragment
2 |
3 | import android.content.res.Configuration
4 | import android.os.Bundle
5 | import android.util.Log
6 | import com.duqian.app.live.R
7 | import com.duqian.app.helper.RegisterEvent
8 | import com.duqian.app.live.controller.AnchorInfoController
9 | import com.duqian.app.live.controller.RoomCommonController
10 | import com.duqian.app.live.utils.setViewVisible
11 | import com.duqian.app.live.base_comm.BaseLiveFragment
12 | import com.duqian.app.live.base_comm.BaseRoomParams
13 | import com.duqian.app.live.base_comm.ParamKey
14 | import com.duqian.app.live.liveroom.data.LiveRoomEvent
15 | import com.duqian.app.live.liveroom.data.LiveRoomEventCode
16 | import kotlinx.android.synthetic.main.fragment_room_main.*
17 | import kotlinx.android.synthetic.main.include_room_top_layout.*
18 | import org.greenrobot.eventbus.Subscribe
19 | import org.greenrobot.eventbus.ThreadMode
20 |
21 | /**
22 | * Description:观众端,直播间,主fragment
23 | * Created by 杜乾 on 2022/8/10 - 16:14.
24 | * E-mail: duqian2010@gmail.com
25 | */
26 | @RegisterEvent
27 | class RoomMainFragment : BaseLiveFragment() {
28 |
29 | //通用逻辑模块(不方便归类,或者无UI逻辑端,可以暂时放这里)
30 | private lateinit var mRoomCommonController: RoomCommonController
31 |
32 | //主播信息/follow模块
33 | private lateinit var mAnchorInfoController: AnchorInfoController
34 |
35 | override fun getLayoutId(): Int {
36 | return R.layout.fragment_room_main
37 | }
38 |
39 | override fun initControllers() {
40 | //通用模块
41 | mRoomCommonController = RoomCommonController(rootCommonContainer, this)
42 | //主播信息
43 | mAnchorInfoController = AnchorInfoController(rootLiveTopContainer, this)
44 | //公屏
45 | addFragment(R.id.rootPublicChatContainer, PublicChatFragment.newInstance(mRoomParams))
46 | //礼物特效
47 | addFragment(R.id.rootEffectContainer, RoomEffectFragment.newInstance(mRoomParams))
48 | //底部按钮
49 | addFragment(R.id.rootBottomContainer, RoomBottomBtnFragment.newInstance(mRoomParams))
50 | }
51 |
52 | override fun initData() {
53 |
54 | }
55 |
56 | override fun initView() {
57 | //rootRoomContainer.setViewVisible(false)
58 | }
59 |
60 | override fun observerData() {
61 | super.observerData()
62 | mGlobalViewModel?.isRoomLoading?.observe(this) {
63 | rootRoomContainer?.setViewVisible(!it)
64 | //Log.d(TAG,"dq isRoomLoading=$it")
65 | }
66 | }
67 |
68 | @Subscribe(threadMode = ThreadMode.MAIN)
69 | fun onRoomEvent(event: LiveRoomEvent) {
70 | when (event.eventCode) {
71 | LiveRoomEventCode.EVENT_ID_LIVE_ROOM_END -> {
72 | }
73 | }
74 | }
75 |
76 | override fun onConfigurationChanged(newConfig: Configuration) {
77 | super.onConfigurationChanged(newConfig)
78 |
79 | Log.d(TAG, "onConfigurationChanged=$")
80 | }
81 |
82 | companion object {
83 | private const val TAG = "LiveRoomFragment"
84 |
85 | /**
86 | * 传入的参数,后续按需扩展,进入房间的参数放BaseRoomParams,其他额外的参数视情况添加
87 | */
88 | fun newInstance(data: BaseRoomParams? = null): RoomMainFragment {
89 | val fragment = RoomMainFragment()
90 | val bundle = Bundle()
91 | if (data != null) {
92 | bundle.putSerializable(ParamKey.PARAM_KEY_ROOM_DATA, data)
93 | //bundle.putParcelable(ParamKey.PARAM_KEY_ROOM_DATA, data)
94 | }
95 | fragment.arguments = bundle
96 | return fragment
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/fragment/RoomTestFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.fragment
2 |
3 | import android.os.Bundle
4 | import com.duqian.app.live.R
5 | import com.duqian.app.helper.ToastUtils
6 | import com.duqian.app.live.services.IRoomApi
7 | import com.duqian.app.live.base_comm.BaseLiveFragment
8 | import com.duqian.app.live.base_comm.BaseRoomParams
9 | import com.duqian.app.live.base_comm.ParamKey
10 | import com.duqian.app.live.base_comm.RoomType
11 | import dagger.hilt.android.AndroidEntryPoint
12 | import kotlinx.android.synthetic.main.fragment_room_test_layout.*
13 | import javax.inject.Inject
14 |
15 | /**
16 | * Description:测试直播间内容的fragment
17 | *
18 | * Created by 杜乾 on 2022/8/11 - 19:20.
19 | * E-mail: duqian2010@gmail.com
20 | */
21 | @AndroidEntryPoint
22 | class RoomTestFragment : BaseLiveFragment() {
23 |
24 | @Inject
25 | lateinit var mRoomApi: IRoomApi
26 |
27 | override fun getLayoutId(): Int {
28 | return R.layout.fragment_room_test_layout
29 | }
30 |
31 | override fun initData() {
32 | ToastUtils.show("mRoomApi: ${mRoomApi.getRoomInfo()}")
33 | }
34 |
35 | override fun initView() {
36 | tvRoomTestTips.setOnClickListener {
37 | mGlobalViewModel?.roomType?.value = RoomType.ROOM_TYPE_SINGLE
38 | }
39 | }
40 |
41 | override fun onConfigurationChanged(isLandscape: Boolean) {
42 | super.onConfigurationChanged(isLandscape)
43 | tvRoomTestTips.text = "isLandscape= $isLandscape"
44 | }
45 |
46 | override fun observerData() {
47 | super.observerData()
48 | }
49 |
50 | companion object {
51 | /**
52 | * 传入的参数,后续按需扩展,进入房间的参数放BaseRoomParams,其他额外的参数视情况添加
53 | */
54 | fun newInstance(data: BaseRoomParams? = null): RoomTestFragment {
55 | val fragment = RoomTestFragment()
56 | val bundle = Bundle()
57 | bundle.putSerializable(ParamKey.PARAM_KEY_ROOM_DATA, data)
58 | fragment.arguments = bundle
59 | return fragment
60 | }
61 | }
62 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/fragment/dialog/RoomGiftDialog.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.fragment.dialog
2 |
3 | import android.os.Bundle
4 | import android.os.Parcelable
5 | import com.duqian.app.live.R
6 | import com.duqian.app.live.databinding.DialogRoomGiftLayoutBinding
7 | import com.duqian.app.live.base_comm.BaseDataBindingDialog
8 | import com.duqian.app.live.base_comm.ParamKey
9 |
10 | /**
11 | * Description:送礼弹窗
12 | *
13 | * Created by 杜乾 on 2022/8/13 - 22:29.
14 | * E-mail: duqian2010@gmail.com
15 | */
16 | class RoomGiftDialog : BaseDataBindingDialog() {
17 |
18 | override val layoutId: Int
19 | get() = R.layout.dialog_room_gift_layout
20 |
21 | override fun iniView() {
22 |
23 | }
24 |
25 | override fun iniData() {
26 | }
27 |
28 | var callback: DialogCallback? = null
29 |
30 | interface DialogCallback {
31 | fun onClicked()
32 | }
33 |
34 | override fun iniListener() {
35 | mDataBinding?.ivDialogImg?.setOnClickListener {
36 | callback?.onClicked()
37 | }
38 | }
39 |
40 | companion object {
41 | /**
42 | * 传入的参数,按需扩展,
43 | */
44 | fun newInstance(data: Parcelable? = null): RoomGiftDialog {
45 | val fragment = RoomGiftDialog()
46 | val bundle = Bundle()
47 | bundle.putParcelable(ParamKey.PARAM_KEY_DIALOG_DATA, data)
48 | fragment.arguments = bundle
49 | return fragment
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/liveroom/LiveRoomActivity.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.liveroom
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.res.Configuration
7 | import android.util.Log
8 | import com.duqian.app.live.R
9 | import com.duqian.app.helper.RegisterEvent
10 | import com.duqian.app.helper.ToastUtils
11 | import com.duqian.app.live.base_comm.*
12 | import com.duqian.app.live.fragment.RoomMainFragment
13 | import com.duqian.app.live.fragment.RoomTestFragment
14 | import com.duqian.app.live.liveroom.controller.RoomLivePlayerController
15 | import com.duqian.app.live.liveroom.data.LiveRoomParams
16 | import com.duqian.app.live.services.IRoomApi
17 | import com.duqian.app.live.utils.FragmentUtils
18 | import com.duqian.app.live.utils.LiveEventBus
19 | import com.duqian.app.live.liveroom.data.LiveRoomEvent
20 | import com.duqian.app.live.liveroom.data.LiveRoomEventCode
21 | import dagger.hilt.android.AndroidEntryPoint
22 | import org.greenrobot.eventbus.Subscribe
23 | import org.greenrobot.eventbus.ThreadMode
24 | import javax.inject.Inject
25 |
26 | /**
27 | * Description:直播间Activity
28 | * 目前没有支持上下滑动,后面直接加入一个触摸事件处理,不使用ViewPager,可以不改动已有的框架
29 | * Created by 杜乾 on 2022/8/10 - 14:05.
30 | * E-mail: duqian2010@gmail.com
31 | */
32 | @RegisterEvent
33 | @AndroidEntryPoint
34 | class LiveRoomActivity : BaseLiveActivity() {
35 |
36 | private var mRoomLivePlayerController: RoomLivePlayerController? = null
37 |
38 | @Inject
39 | lateinit var mRoomApi: IRoomApi
40 |
41 | override fun getLayoutId(): Int {
42 | return R.layout.activity_live_room
43 | }
44 |
45 | override fun initData() {
46 | super.initData()
47 | val roomInfo = mRoomApi.getRoomInfo() ?: ""
48 | ToastUtils.show(roomInfo)
49 | }
50 |
51 | override fun initView() {
52 | super.initView()
53 | //主fragment,放在roomType改变的时候处理
54 | }
55 |
56 | override fun observerData() {
57 | super.observerData()
58 | mGlobalViewModel.roomType.observe(this) {
59 | initRoomFragmentByRoomType()
60 | }
61 |
62 | // TODO-dq: remove 清屏
63 | LiveEventBus.get().with(LiveEventKey.EVENT_KEY_SEND_GIFT_START, Boolean::class.java)
64 | .observe(this) {
65 | if (it)
66 | FragmentUtils.removeFragmentByTag(
67 | supportFragmentManager,
68 | RoomMainFragment::class.java.simpleName
69 | )
70 | Log.d(TAG, "dq EVENT_KEY_CLEAR_LIVE_SCREEN $it")
71 | }
72 | }
73 |
74 | override fun initControllers() {
75 | super.initControllers()
76 | mRoomLivePlayerController = RoomLivePlayerController(mRootView, this)
77 | }
78 |
79 | /**
80 | * 初始化房间类型,方便拓展/修改主体内容,根据直播间类型的初始化
81 | */
82 | private fun initRoomFragmentByRoomType() {
83 | var mainFragment: BaseLiveFragment = RoomMainFragment.newInstance(mRoomParams)
84 | when (mGlobalViewModel.roomType.value) {
85 | RoomType.ROOM_TYPE_SINGLE -> {
86 | mainFragment = RoomMainFragment.newInstance(mRoomParams)
87 | }
88 | RoomType.ROOM_TYPE_DOUBLE -> {//todo-dq test remove
89 | mainFragment = RoomTestFragment.newInstance(mRoomParams)
90 | }
91 | }
92 | FragmentUtils.addFragment(
93 | supportFragmentManager, R.id.rootRoomContainer, mainFragment
94 | )
95 | }
96 |
97 | @Subscribe(threadMode = ThreadMode.MAIN)
98 | fun onRoomEvent(event: LiveRoomEvent) {
99 | when (event.eventCode) {
100 | LiveRoomEventCode.EVENT_ID_LIVE_ROOM_END -> {
101 | ToastUtils.show("Live End")
102 | }
103 | }
104 | }
105 |
106 | override fun onConfigurationChanged(newConfig: Configuration) {
107 | super.onConfigurationChanged(newConfig)
108 | val isLandscape = newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
109 | mGlobalViewModel.isLandscape.value = isLandscape
110 | Log.d(TAG, "dq-isLandscape=$isLandscape")
111 | }
112 |
113 | override fun onDestroy() {
114 | super.onDestroy()
115 | //todo-dq 销毁资源
116 | }
117 |
118 | companion object {
119 | private const val TAG = "LiveRoomActivity"
120 |
121 | fun startActivity(context: Context?, roomParams: LiveRoomParams) {
122 | val intent = Intent(context, LiveRoomActivity::class.java)
123 | roomParams.isPushRoom = false
124 | intent.putExtra(ParamKey.PARAM_KEY_ROOM_DATA, roomParams)
125 | if (context !is Activity) {
126 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
127 | }
128 | context?.startActivity(intent)
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/liveroom/controller/RoomCallBtnController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.liveroom.controller
2 |
3 | import android.view.View
4 | import android.widget.ImageView
5 | import androidx.lifecycle.LifecycleOwner
6 | import com.duqian.app.live.R
7 | import com.duqian.app.helper.ToastUtils
8 | import com.duqian.app.live.base_comm.BaseController
9 |
10 | /**
11 | * Description:直播间底部,打电话按钮的处理逻辑
12 | *
13 | * Created by 杜乾 on 2022/8/11 - 19:43.
14 | * E-mail: duqian2010@gmail.com
15 | */
16 | class RoomCallBtnController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
17 |
18 | companion object {
19 | private const val TAG = "RoomCallBtnController"
20 | }
21 |
22 | override fun initView(rootView: View) {
23 | rootView.findViewById(R.id.ivRoomCall).setOnClickListener {
24 | ToastUtils.show("打电话")
25 | }
26 | }
27 |
28 | override fun onResume() {
29 | }
30 |
31 | override fun onDestroy() {
32 | }
33 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/liveroom/controller/RoomCoinsBtnController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.liveroom.controller
2 |
3 | import android.view.View
4 | import android.widget.ImageView
5 | import androidx.lifecycle.LifecycleOwner
6 | import com.duqian.app.live.R
7 | import com.duqian.app.helper.ToastUtils
8 | import com.duqian.app.live.base_comm.BaseController
9 | /**
10 | * Description:直播间底部,充值按钮的处理逻辑
11 | *
12 | * Created by 杜乾 on 2022/8/11 - 19:43.
13 | * E-mail: duqian2010@gmail.com
14 | */
15 | class RoomCoinsBtnController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
16 |
17 | companion object {
18 | private const val TAG = "RoomCoinsBtnController"
19 | }
20 |
21 | override fun initView(rootView: View) {
22 | rootView.findViewById(R.id.ivRoomCoins).setOnClickListener {
23 | ToastUtils.show("弹起充值面板")
24 | }
25 | }
26 |
27 | override fun onResume() {
28 |
29 | }
30 |
31 | override fun onDestroy() {
32 |
33 | }
34 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/liveroom/data/LiveRoomEvent.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.liveroom.data
2 |
3 | import com.duqian.app.live.base_comm.BaseEvent
4 |
5 | /**
6 | * Description:直播间通用事件
7 | * Created by 杜小菜 on 2022/8/10 - 12:48.
8 | * E-mail: duqian2010@gmail.com
9 | */
10 | /**
11 | * 观众端,通用事件
12 | */
13 | class LiveRoomEvent(eventCode: Int, data: Any? = null) : BaseEvent(eventCode, data) {
14 | }
15 |
16 | //Note 尽量通过eventType区分事件类型,而不是重复新建各种event类。如果要新建,后续在这里补充添加其他直播间的事件类型,
17 |
18 | //定义事件常量,新增的事件id,注意自增,不要写重复了,遇到冲突的时候,确保id不一样即可。
19 | object LiveRoomEventCode {
20 | private const val EVENT_ID_LIVE_ROOM_BASE = 0x1000 //直播间模块基准id
21 | const val EVENT_ID_LIVE_ROOM_ENTER = EVENT_ID_LIVE_ROOM_BASE + 1 //进入房间的事件
22 | const val EVENT_ID_LIVE_ROOM_END = EVENT_ID_LIVE_ROOM_BASE + 2 // 退出直播间的事件
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/liveroom/data/LiveRoomParams.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.liveroom.data
2 |
3 | import com.duqian.app.live.base_comm.BaseRoomParams
4 |
5 | /**
6 | * Description:外部进入直播房间,传参数做下封装
7 | * Created by 杜乾 on 2022/8/10 - 18:23.
8 | * E-mail: duqian2010@gmail.com
9 | */
10 | class LiveRoomParams(roomId: Int, from: String? = "") :
11 | BaseRoomParams(roomId, from) {
12 |
13 | override fun toString(): String {
14 | return "RoomParams(roomId=$roomId, from=$from, mRoomType=$roomType)"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/pushroom/PushRoomActivity.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.pushroom
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.pm.ActivityInfo
7 | import android.os.Bundle
8 | import com.duqian.app.live.R
9 | import com.duqian.app.helper.RegisterEvent
10 | import com.duqian.app.live.fragment.RoomMainFragment
11 | import com.duqian.app.live.pushroom.controller.PushCameraController
12 | import com.duqian.app.live.pushroom.data.PushRoomEvent
13 | import com.duqian.app.live.pushroom.data.PushRoomEventCode
14 | import com.duqian.app.live.pushroom.data.PushRoomParams
15 | import com.duqian.app.live.pushroom.ui.LivePreviewFragment
16 | import com.duqian.app.live.utils.FragmentUtils
17 | import com.duqian.app.live.base_comm.BaseLiveActivity
18 | import com.duqian.app.live.base_comm.ParamKey
19 | import com.duqian.app.live.liveroom.data.LiveRoomEvent
20 | import com.duqian.app.live.liveroom.data.LiveRoomEventCode
21 | import com.duqian.app.live.pushroom.controller.AgoraPusherController
22 | import org.greenrobot.eventbus.Subscribe
23 | import org.greenrobot.eventbus.ThreadMode
24 |
25 | /**
26 | * Description:主播端,push直播间
27 | * Created by 杜乾 on 2022/8/10 - 14:05.
28 | * E-mail: duqian2010@gmail.com
29 | */
30 | @RegisterEvent
31 | class PushRoomActivity : BaseLiveActivity() {
32 |
33 | //CameraX预览
34 | private var mPushCameraController: PushCameraController? = null
35 |
36 | //声网推流
37 | private var mAgoraPusherController: AgoraPusherController? = null
38 |
39 | override fun onCreate(savedInstanceState: Bundle?) {
40 | super.onCreate(savedInstanceState)
41 | requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
42 | }
43 |
44 | override fun getLayoutId(): Int {
45 | return R.layout.activity_live_push_room
46 | }
47 |
48 | override fun initView() {
49 | super.initView()
50 | initFragments()
51 | }
52 |
53 | override fun initControllers() {
54 | //mPushCameraController = PushCameraController(mRootView, this)
55 | mAgoraPusherController = AgoraPusherController(mRootView, this)
56 | }
57 |
58 | private fun initFragments() {
59 | FragmentUtils.addFragment(
60 | supportFragmentManager, R.id.rootRoomContainer,
61 | LivePreviewFragment.newInstance()
62 | )
63 | }
64 |
65 | @Subscribe(threadMode = ThreadMode.MAIN)
66 | fun onRoomEvent(event: LiveRoomEvent) {
67 | when (event.eventCode) {
68 | LiveRoomEventCode.EVENT_ID_LIVE_ROOM_END -> {
69 | // TODO-dq: 处理下播逻辑
70 | }
71 | }
72 | }
73 |
74 | @Subscribe(threadMode = ThreadMode.MAIN)
75 | fun onRoomEvent(event: PushRoomEvent) {
76 | when (event.eventCode) {
77 | PushRoomEventCode.EVENT_ID_PUSH_ROOM_START_LIVE -> {
78 | FragmentUtils.addFragment(
79 | supportFragmentManager, R.id.rootRoomContainer,
80 | RoomMainFragment.newInstance(mRoomParams)
81 | )
82 | }
83 | }
84 | }
85 |
86 | companion object {
87 | fun startActivity(context: Context?, roomParams: PushRoomParams) {
88 | val intent = Intent(context, PushRoomActivity::class.java)
89 | roomParams.isPushRoom = true
90 | intent.putExtra(ParamKey.PARAM_KEY_ROOM_DATA, roomParams)
91 | if (context !is Activity) {
92 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
93 | }
94 | context?.startActivity(intent)
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/pushroom/controller/AgoraPusherController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.pushroom.controller
2 |
3 | import android.util.Log
4 | import android.view.View
5 | import android.widget.FrameLayout
6 | import androidx.lifecycle.LifecycleOwner
7 | import com.duqian.app.base.BaseApplication
8 | import com.duqian.app.helper.RegisterEvent
9 | import com.duqian.app.live.R
10 | import com.duqian.app.live.base_comm.BaseController
11 | import com.duqian.app.live.liveroom.data.LiveRoomEvent
12 | import com.duqian.app.live.liveroom.data.LiveRoomEventCode
13 | import com.duqian.app.live.pushroom.data.PushRoomEvent
14 | import com.duqian.app.live.pushroom.data.PushRoomEventCode
15 | import com.duqian.app.live.services.AutoServiceHelper
16 | import com.duqian.live.pusher.AgoraPusherImpl.Companion.TEST_PUBLISH_URL
17 | import com.duqian.live.pusher.IBasePusher
18 | import com.duqian.live.pusher.ResultCode
19 | import org.greenrobot.eventbus.Subscribe
20 | import org.greenrobot.eventbus.ThreadMode
21 |
22 | /**
23 | * Description:声网sdk推流逻辑
24 | *
25 | * Created by 杜乾 on 2022/8/18 - 21:11.
26 | * E-mail: duqian2010@gmail.com
27 | */
28 |
29 | @RegisterEvent
30 | class AgoraPusherController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
31 |
32 | private lateinit var previewView: FrameLayout
33 | private val mAgoraPush: IBasePusher? = AutoServiceHelper.load(IBasePusher::class.java)
34 |
35 | override fun initView(rootView: View) {
36 | previewView = rootView.findViewById(R.id.agoraContainer)
37 | }
38 |
39 | override fun initData() {
40 | super.initData()
41 | mAgoraPush?.init(BaseApplication.instance.baseContext, previewView)
42 | //禁止声音
43 | //mAgoraPush?.enableAudioVideo(false, false)
44 | //mAgoraPush?.muteLocalAudioStream(true)
45 | }
46 |
47 | @Subscribe(threadMode = ThreadMode.MAIN)
48 | fun onRoomEvent(event: LiveRoomEvent) {
49 | when (event.eventCode) {
50 | LiveRoomEventCode.EVENT_ID_LIVE_ROOM_END -> {
51 | //重复调用,会失败 ERR_LEAVE_CHANNEL_REJECTED(18):离开频道失败
52 | }
53 | }
54 | }
55 |
56 | @Subscribe(threadMode = ThreadMode.MAIN)
57 | fun onPushRoomEvent(event: PushRoomEvent) {
58 | when (event.eventCode) {
59 | PushRoomEventCode.EVENT_ID_PUSH_ROOM_START_LIVE -> {
60 | val joinChannel = mAgoraPush?.joinChannel()
61 | if (joinChannel == ResultCode.STATUS_OK) {
62 | val startPushToCDN = mAgoraPush?.startPushToCDN(TEST_PUBLISH_URL)
63 | Log.d(TAG, "joinChannel=$joinChannel,startPushToCDN=$startPushToCDN")
64 | } else {
65 | Log.d(TAG, "joinChannel,error=$joinChannel")
66 | }
67 | }
68 |
69 | PushRoomEventCode.EVENT_PUSH_MIC_BTN_CLICKED -> {
70 | val isMicEnable = event.data as Boolean
71 | mAgoraPush?.enableAudioVideo(
72 | false,
73 | isMicEnable
74 | )
75 | Log.d(TAG, "isMicEnable=$isMicEnable")
76 | mAgoraPush?.muteLocalAudioStream(!isMicEnable)
77 | /*if (isMicEnable) {
78 | mAgoraPush?.adjustRecordingSignalVolume(100)
79 | } else {
80 | mAgoraPush?.adjustRecordingSignalVolume(0)
81 | }*/
82 | }
83 | }
84 | }
85 |
86 | override fun onResume() {
87 | // TODO: 声网的这个处理有问题,停止预览,无法恢复,暂时不处理
88 | val startPreview = mAgoraPush?.startPreview()
89 | Log.d(TAG, "startPreview=$startPreview")
90 | //mAgoraPush?.onLifecycleChanged(LifecycleEvent.ON_RESUME)
91 | }
92 |
93 | override fun onPause() {
94 | val stopPreview = mAgoraPush?.stopPreview()
95 | Log.d(TAG, "startPreview=$stopPreview")
96 | //mAgoraPush?.onLifecycleChanged(LifecycleEvent.ON_PAUSE)
97 | }
98 |
99 | override fun onDestroy() {
100 | mAgoraPush?.release()
101 | }
102 |
103 | companion object {
104 | private const val TAG = "dq-agora-pusher"
105 | }
106 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/pushroom/controller/PushCameraController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.pushroom.controller
2 |
3 | import android.view.View
4 | import androidx.camera.core.CameraSelector
5 | import androidx.camera.core.Preview
6 | import androidx.camera.lifecycle.ProcessCameraProvider
7 | import androidx.camera.view.PreviewView
8 | import androidx.core.content.ContextCompat
9 | import androidx.lifecycle.LifecycleOwner
10 | import com.duqian.app.live.R
11 | import com.duqian.app.live.base_comm.BaseController
12 | import com.google.common.util.concurrent.ListenableFuture
13 | import com.duqian.app.live.utils.setViewVisible
14 |
15 | /**
16 | * Description:开播端,基于CameraX的摄像头预览逻辑
17 | *
18 | * Created by 杜乾 on 2022/8/11 - 19:43.
19 | * E-mail: duqian2010@gmail.com
20 | */
21 | class PushCameraController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
22 |
23 | private var cameraProviderFuture: ListenableFuture? = null
24 |
25 | private lateinit var previewView: PreviewView
26 | override fun initView(rootView: View) {
27 | previewView = rootView.findViewById(R.id.previewView)
28 | previewView.setViewVisible(true)
29 | initCamera()
30 | }
31 |
32 | private fun initCamera() {
33 | cameraProviderFuture = ProcessCameraProvider.getInstance(mActivity)
34 | cameraProviderFuture?.addListener({
35 | val cameraProvider = cameraProviderFuture!!.get()
36 | bindPreview(cameraProvider)
37 | }, ContextCompat.getMainExecutor(mActivity))
38 | }
39 |
40 | private fun bindPreview(cameraProvider: ProcessCameraProvider) {
41 | val preview = Preview.Builder().build()
42 |
43 | val cameraSelector = CameraSelector.Builder()
44 | .requireLensFacing(CameraSelector.LENS_FACING_BACK)
45 | //.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
46 | .build()
47 |
48 | preview.setSurfaceProvider(previewView.surfaceProvider)
49 |
50 | val camera = cameraProvider.bindToLifecycle(mLifecycleOwner, cameraSelector, preview)
51 | }
52 |
53 | override fun onResume() {
54 |
55 | }
56 |
57 | override fun onDestroy() {
58 |
59 | }
60 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/pushroom/controller/PushMicBtnController.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.pushroom.controller
2 |
3 | import android.Manifest
4 | import android.view.View
5 | import android.widget.ImageView
6 | import androidx.lifecycle.LifecycleOwner
7 | import com.duqian.app.helper.EventBusHelper
8 | import com.duqian.app.helper.ToastUtils
9 | import com.duqian.app.live.R
10 | import com.duqian.app.live.base_comm.BaseController
11 | import com.duqian.app.live.pushroom.data.PushRoomEvent
12 | import com.duqian.app.live.pushroom.data.PushRoomEventCode
13 |
14 | /**
15 | * Description:开播端,底部,禁音 按钮的处理逻辑
16 | *
17 | * Created by 杜乾 on 2022/8/11 - 19:43.
18 | * E-mail: duqian2010@gmail.com
19 | */
20 | class PushMicBtnController(view: View, owner: LifecycleOwner) : BaseController(view, owner) {
21 |
22 | private lateinit var ivMicBtn: ImageView
23 | private var isMicOn = true
24 | override fun initView(rootView: View) {
25 | ivMicBtn = rootView.findViewById(R.id.ivRoomMicVoice)
26 | ivMicBtn.setOnClickListener {
27 | if (hasMicPermission()) {
28 | isMicOn = !isMicOn
29 | updateMicIcon()
30 |
31 | EventBusHelper.sendEvent(
32 | PushRoomEvent(
33 | PushRoomEventCode.EVENT_PUSH_MIC_BTN_CLICKED,
34 | isMicOn
35 | )
36 | )
37 | } else {
38 | ToastUtils.show("No audio permission")
39 | }
40 | }
41 | }
42 |
43 | private fun hasMicPermission(): Boolean {
44 | return true //todo-dq 权限检测不正常,
45 | //return CommonUtils.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO)
46 | }
47 |
48 | private fun updateMicIcon() {
49 | if (isMicOn) {
50 | ivMicBtn.setImageResource(R.mipmap.ic_live_pushroom_btn_mic_on)
51 | } else {
52 | ivMicBtn.setImageResource(R.mipmap.ic_live_pushroom_btn_mic_off)
53 | }
54 | }
55 |
56 | override fun onResume() {
57 | }
58 |
59 | override fun onDestroy() {
60 | }
61 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/pushroom/data/PushRoomEvent.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.pushroom.data
2 |
3 | import com.duqian.app.live.base_comm.BaseEvent
4 |
5 | /**
6 | * 主播端,通用事件,一般很多事件会和直播间一样,这个类只定义主播端不一样的id
7 | * 尽量通过eventType区分事件类型,而不是重复新建各种event类。如果要新建,后续在这里补充添加其他直播间的事件类型,
8 | *
9 | * Created by 杜小菜 on 2022/8/10 - 12:48.
10 | * E-mail: duqian2010@gmail.com
11 | */
12 | class PushRoomEvent(eventCode: Int, data: Any? = null) : BaseEvent(eventCode, data) {
13 | }
14 |
15 | object PushRoomEventCode {
16 | //定义事件常量,新增的事件id,注意自增,不要写重复了,遇到冲突的时候,确保id不一样即可。
17 | private const val EVENT_ID_PUSH_ROOM_BASE = 2000 //主播端模块基准id
18 | const val EVENT_ID_PUSH_ROOM_START_LIVE = EVENT_ID_PUSH_ROOM_BASE + 1 //开播事件
19 | const val EVENT_ID_PUSH_ROOM_LEAVE = EVENT_ID_PUSH_ROOM_BASE + 2 // 主播主动离开的事件
20 | const val EVENT_PUSH_MIC_BTN_CLICKED = EVENT_ID_PUSH_ROOM_BASE + 3 // 主播禁音/恢复声音
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/pushroom/data/PushRoomParams.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.pushroom.data
2 |
3 | import com.duqian.app.live.base_comm.BaseRoomParams
4 |
5 | /**
6 | * Description:外部进入开播端,传参数做下封装
7 | * Created by 杜乾 on 2022/8/10 - 18:23.
8 | * E-mail: duqian2010@gmail.com
9 | */
10 | class PushRoomParams(roomId: Int, from: String? = "") :
11 | BaseRoomParams(roomId, from) {
12 |
13 | override fun toString(): String {
14 | return "RoomParams(roomId=$roomId, from=$from, mRoomType=$roomType)"
15 | }
16 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/pushroom/ui/LivePreviewFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.pushroom.ui
2 |
3 | import android.os.Bundle
4 | import com.duqian.app.live.R
5 | import com.duqian.app.helper.EventBusHelper
6 | import com.duqian.app.helper.RegisterEvent
7 | import com.duqian.app.live.utils.setViewVisible
8 | import com.duqian.app.live.base_comm.BaseLiveFragment
9 | import com.duqian.app.live.base_comm.ParamKey
10 | import com.duqian.app.live.pushroom.data.PushRoomEvent
11 | import com.duqian.app.live.pushroom.data.PushRoomEventCode
12 | import kotlinx.android.synthetic.main.fragment_pushroom_preview.*
13 | import org.greenrobot.eventbus.Subscribe
14 | import org.greenrobot.eventbus.ThreadMode
15 | import java.io.Serializable
16 |
17 | /**
18 | * Description:开播预览界面
19 | * Created by 杜乾 on 2022/8/10 - 18:10.
20 | * E-mail: duqian2010@gmail.com
21 | */
22 | @RegisterEvent
23 | class LivePreviewFragment : BaseLiveFragment() {
24 | override fun getLayoutId(): Int {
25 | return R.layout.fragment_pushroom_preview
26 | }
27 |
28 | override fun initData() {
29 |
30 | }
31 |
32 | override fun initView() {
33 |
34 | }
35 |
36 | override fun initListener() {
37 | tvStartLive.setOnClickListener {
38 | //todo-dq 请求接口,成功后,隐藏当前按钮,切换显示直播间主体内容
39 | tvStartLive.setViewVisible(false)
40 | EventBusHelper.sendEvent(PushRoomEvent(PushRoomEventCode.EVENT_ID_PUSH_ROOM_START_LIVE))
41 | }
42 | }
43 |
44 | override fun observerData() {
45 | }
46 |
47 |
48 | @Subscribe(threadMode = ThreadMode.MAIN)
49 | fun onRoomEvent(event: PushRoomEvent) {
50 | when (event.eventCode) {
51 | //PushRoomEventCode.EVENT_ID_PUSH_ROOM_START_LIVE -> { }
52 | }
53 | }
54 |
55 | companion object {
56 | private const val TAG = "LivePreviewFragment"
57 |
58 | /**
59 | * 传入的参数,后续按需扩展,进入房间的参数放BaseRoomParams,其他额外的参数视情况添加
60 | */
61 | fun newInstance(data: Serializable? = null): LivePreviewFragment {
62 | val fragment = LivePreviewFragment()
63 | val bundle = Bundle()
64 | if (data != null) {
65 | bundle.putSerializable(ParamKey.PARAM_KEY_ROOM_DATA, data)
66 | }
67 | fragment.arguments = bundle
68 | return fragment
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/repository/RoomApiImpl.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.repository
2 |
3 | import com.duqian.app.live.services.IRoomApi
4 | import javax.inject.Inject
5 |
6 | /**
7 | * Description:接口实现类
8 | *
9 | * Created by 杜乾 on 2022/8/14 - 12:50.
10 | * E-mail: duqian2010@gmail.com
11 | */
12 | class RoomApiImpl @Inject constructor() : IRoomApi {
13 | override fun getRoomInfo(): String? {
14 | return "度小菜 roomInfo"
15 | }
16 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/repository/RoomModule.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.repository
2 |
3 | import com.duqian.app.live.services.IRoomApi
4 | import dagger.Binds
5 | import dagger.Module
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.android.components.ActivityComponent
8 |
9 | /**
10 | * Description:房间Module提供者
11 | *
12 | * Created by 杜乾 on 2022/8/14 - 14:18.
13 | * E-mail: duqian2010@gmail.com
14 | */
15 | @InstallIn(ActivityComponent::class)
16 | @Module
17 | abstract class RoomModule {
18 |
19 | @Binds
20 | abstract fun bindRoomApi(impl: RoomApiImpl): IRoomApi
21 | }
22 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/services/AutoServiceHelper.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.services
2 |
3 | import android.app.Application
4 | import java.util.*
5 |
6 | /**
7 | * Description:AutoService的load工具方法,todo-dq 处理有多种接口实现的get
8 | *
9 | * Created by 杜乾 on 2022/8/14 - 00:28.
10 | * E-mail: duqian2010@gmail.com
11 | */
12 | object AutoServiceHelper {
13 |
14 | fun load(clazz: Class): S? {
15 | val service = ServiceLoader.load(clazz).iterator()
16 | try {
17 | if (service.hasNext()) {
18 | return service.next()
19 | }
20 | } catch (e: Exception) {
21 | e.printStackTrace()
22 | }
23 | return null
24 | }
25 |
26 | fun load(app: Application, service: Class): ServiceLoader {
27 | return ServiceLoader.load(service, app.classLoader)
28 | }
29 |
30 | fun loadFirst(app: Application, service: Class): S {
31 | return load(app, service).iterator().next()
32 | }
33 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/services/IRoomApi.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.services
2 |
3 | /**
4 | * Description:房间接口API
5 | *
6 | * Created by 杜乾 on 2022/8/14 - 11:54.
7 | * E-mail: duqian2010@gmail.com
8 | */
9 |
10 | interface IRoomApi {
11 | fun getRoomInfo(): String?
12 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/utils/FragmentUtils.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.utils
2 |
3 | import androidx.fragment.app.FragmentActivity
4 | import android.app.Activity
5 | import android.text.TextUtils
6 | import android.util.Log
7 | import androidx.fragment.app.DialogFragment
8 | import androidx.fragment.app.Fragment
9 | import androidx.fragment.app.FragmentManager
10 |
11 | /**
12 | * Description:Fragment工具类
13 | * Created by 杜乾 on 2022/8/10 - 15:26.
14 | * E-mail: duqian2010@gmail.com
15 | */
16 | object FragmentUtils {
17 |
18 | private const val TAG = "FragmentUtils"
19 |
20 | fun show(activity: FragmentActivity?, dialogFragment: DialogFragment?) {
21 | if (activity != null) {
22 | show(activity, activity.supportFragmentManager, false, dialogFragment)
23 | }
24 | }
25 |
26 | fun show(
27 | activity: Activity?, fragmentMgr: FragmentManager?, ignoreShowOnce: Boolean,
28 | dialogFragment: DialogFragment?
29 | ) {
30 | if (dialogFragment == null) {
31 | return
32 | }
33 | //val tag = dialogFragment.javaClass.simpleName
34 | val tag = dialogFragment::class.java.simpleName
35 | show(activity, fragmentMgr, ignoreShowOnce, dialogFragment, tag)
36 | }
37 |
38 | fun show(
39 | activity: Activity?,
40 | manager: FragmentManager?,
41 | fragment: DialogFragment,
42 | tag: String?
43 | ) {
44 | show(activity, manager, false, fragment, tag)
45 | }
46 |
47 | fun show(
48 | activity: Activity?,
49 | manager: FragmentManager?,
50 | ignoreShowOnce: Boolean,
51 | fragment: DialogFragment,
52 | tag: String?
53 | ) {
54 | //ignoreShowOnce,忽略只能展示一次
55 | if (activity == null || manager == null ||
56 | !ignoreShowOnce && (manager.findFragmentByTag(tag) != null || fragment.isAdded)
57 | ) {
58 | return
59 | }
60 | showDialogFragmentByCommitAllowingStateLoss(activity, manager, fragment, tag)
61 | }
62 |
63 | fun dismissAllowingStateLoss(fragmentMgr: FragmentManager?, tag: String?) {
64 | if (fragmentMgr != null) {
65 | val fragment = fragmentMgr.findFragmentByTag(tag)
66 | if (fragment is DialogFragment) {
67 | fragment.dismissAllowingStateLoss()
68 | }
69 | }
70 | }
71 |
72 | /**
73 | * 关闭DialogFragment(全局DialogFragment属性使用时,可以先dismiss再重新show避免出现:“java.lang.IllegalStateException: Fragment already
74 | * added:”)
75 | */
76 | fun dismissAllowingStateLoss(fragment: DialogFragment?): Boolean {
77 | val needDismiss = fragment != null && fragment.fragmentManager != null
78 | if (needDismiss) {
79 | fragment!!.dismissAllowingStateLoss()
80 | }
81 | return needDismiss
82 | }
83 |
84 | fun findFragmentByTag(fragmentActivity: FragmentActivity, tag: String?): Boolean {
85 | return fragmentActivity.supportFragmentManager.findFragmentByTag(tag) != null
86 | }
87 |
88 | fun findFragmentByTag(fm: FragmentManager, tag: String?): Fragment? {
89 | return fm.findFragmentByTag(tag)
90 | }
91 |
92 | fun findByTag(fm: FragmentManager?, clazz: Class): T? {
93 | val tag = clazz.simpleName
94 | return if (fm == null) {
95 | null
96 | } else fm.findFragmentByTag(tag) as T?
97 | }
98 |
99 | fun hasFragmentByTag(fragmentActivity: FragmentActivity, tag: String): Boolean {
100 | return fragmentActivity.supportFragmentManager.findFragmentByTag(tag) != null
101 | }
102 |
103 | fun isFragmentShowing(fragmentMgr: FragmentManager?, tag: String?): Boolean {
104 | if (fragmentMgr?.findFragmentByTag(tag) != null) { //instanceof DialogFragment
105 | val fragment = fragmentMgr.findFragmentByTag(tag)
106 | return fragment != null && fragment.isAdded
107 | }
108 | return false
109 | }
110 |
111 | fun addFragment(fm: FragmentManager?, layoutId: Int, fragment: Fragment?) {
112 | if (fm == null || fragment == null) {
113 | return
114 | }
115 | fm.beginTransaction().replace(layoutId, fragment, fragment.javaClass.simpleName)
116 | .commitAllowingStateLoss()
117 | }
118 |
119 | fun removeFragment(fm: FragmentManager?, fragment: Fragment?) {
120 | try {
121 | if (fm == null || fragment == null || !fragment.isAdded) {
122 | return
123 | }
124 | fm.beginTransaction().remove(fragment).commitAllowingStateLoss()
125 | } catch (e: Exception) {
126 | Log.e(TAG, "removeFragment error $e")
127 | }
128 | }
129 |
130 | fun removeFragmentByTag(fm: FragmentManager?, fragmentTag: String? = "") {
131 | if (TextUtils.isEmpty(fragmentTag)) return
132 | val fragmentByTag =
133 | fm?.findFragmentByTag(fragmentTag)
134 | removeFragment(fm, fragmentByTag)
135 | }
136 |
137 | /**
138 | * 直接show,可能报java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
139 | * 关闭DialogFragment,推荐使用commitAllowingStateLoss()方法
140 | */
141 | private fun showDialogFragmentByCommitAllowingStateLoss(
142 | activity: Activity?, fm: FragmentManager?,
143 | dialogFragment: DialogFragment?, tag: String?
144 | ) {
145 | if (fm != null && dialogFragment != null &&
146 | tag != null && !UIUtil.isActivityIllegal(activity)
147 | ) {
148 | val ft = fm.beginTransaction()
149 | ft.add(dialogFragment, tag)
150 | ft.commitAllowingStateLoss()
151 | if (UIUtil.isOnUiThread) {
152 | fm.executePendingTransactions()
153 | } else {
154 | activity?.runOnUiThread { fm.executePendingTransactions() }
155 | }
156 | }
157 | }
158 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/utils/KotlinExtUtils.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.utils
2 |
3 | import android.content.res.Resources
4 | import android.view.View
5 |
6 | /**
7 | * Description:description:Kotlin扩展系统函数
8 | * Created by 杜小菜 on 2022/8/1 - 07:59.
9 | * E-mail: duqian2010@gmail.com
10 | */
11 | // View Extensions
12 | var View.isVisible
13 | get() = visibility == View.VISIBLE
14 | set(value) {
15 | visibility = if (value) View.VISIBLE else View.GONE
16 | }
17 |
18 | fun View.setViewVisible(isVisible: Boolean) {
19 | visibility = if (isVisible) View.VISIBLE else View.GONE
20 | }
21 |
22 |
23 | fun Float.dp2px(): Float {
24 | return (0.5f + this * Resources.getSystem().displayMetrics.density)
25 | }
26 |
27 | fun Int.dp2px(): Float {
28 | return this * 1.0f.dp2px()
29 | }
30 |
31 | val Float.dp: Float
32 | get() = android.util.TypedValue.applyDimension(
33 | android.util.TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics
34 | )
35 |
36 | val Int.dp: Int
37 | get() = (this * 1.0f.dp).toInt()
38 |
39 | val Int.dpInt: Int
40 | get() = this * 1f.dp.toInt()
41 |
42 | val Int.dpFloat: Float
43 | get() = this * 1f.dp
44 |
45 |
46 | val Float.sp: Float
47 | get() = android.util.TypedValue.applyDimension(
48 | android.util.TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics
49 | )
50 |
51 | val Int.sp: Float
52 | get() = this * 1.0f.sp
53 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/utils/LiveEventBus.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.utils
2 |
3 | import androidx.lifecycle.*
4 |
5 | /**
6 | * Description:基于LiveData的事件通信
7 | * Created by 杜乾 on 2022/8/11 - 11:04.
8 | * E-mail: duqian2010@gmail.com
9 | */
10 | class LiveEventBus private constructor() {
11 | private val mLiveDataMap: MutableMap>
12 |
13 | /**
14 | * 存、取LiveData
15 | */
16 | @Synchronized
17 | fun with(key: String, clazz: Class?): BusMutableLiveData {
18 | if (!mLiveDataMap.containsKey(key)) {
19 | mLiveDataMap[key] = BusMutableLiveData()
20 | }
21 | return mLiveDataMap[key] as BusMutableLiveData
22 | }
23 |
24 | @Synchronized
25 | fun with(key: String): BusMutableLiveData {
26 | return with(key, Any::class.java)
27 | }
28 |
29 | /**
30 | * 去除粘性事件,BusMutableLiveData isDisableSticky为true的都要清空
31 | */
32 | fun clear() {
33 | val saveMap: MutableMap> = HashMap()
34 | mLiveDataMap.forEach {
35 | val busMutableLiveData = it.value
36 | if (busMutableLiveData.isDisableSticky) {
37 | //要移除 Log.d(TAG, "removed $busMutableLiveData")
38 | } else {
39 | saveMap[it.key] = busMutableLiveData
40 | }
41 | }
42 | mLiveDataMap.clear()
43 | if (saveMap.isNotEmpty()) {
44 | mLiveDataMap.putAll(saveMap)
45 | }
46 | }
47 |
48 | class BusMutableLiveData : MutableLiveData() {
49 | //是否需要阻止粘性事件,true则不会粘性传递
50 | var isDisableSticky = true
51 |
52 | fun observe(
53 | lifecycleOwner: LifecycleOwner,
54 | isDisableSticky: Boolean,
55 | observer: Observer
56 | ) {
57 | this.isDisableSticky = isDisableSticky
58 | observe(lifecycleOwner, observer)
59 | }
60 |
61 | //重写observe的方法
62 | override fun observe(lifecycleOwner: LifecycleOwner, observer: Observer) {
63 | if (isDisableSticky) {// 反射可能兼容性问题,避免有bug,组件onDestroy处理,保留粘性事件
64 | removeEventWhenDestroy(lifecycleOwner, this, observer)
65 | //改变observer.mLastVersion >= mVersion这个判断 然后拦截onChanged
66 | /*try {
67 | hook(observer as Observer)
68 | } catch (e: Exception) {
69 | Log.d("dq-live", "hook Observer error $e")
70 | super.observe(lifecycleOwner, observer)
71 | }*/
72 | } else {
73 | super.observe(lifecycleOwner, observer)
74 | }
75 | super.observe(lifecycleOwner, observer)
76 | }
77 |
78 | private fun removeEventWhenDestroy(
79 | lifecycleOwner: LifecycleOwner,
80 | busMutableLiveData: BusMutableLiveData,
81 | observer: Observer
82 | ) {
83 | lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
84 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
85 | if (event == Lifecycle.Event.ON_DESTROY) {
86 | busMutableLiveData.removeObserver(observer)
87 | lifecycleOwner.lifecycle.removeObserver(this)
88 | }
89 | }
90 | })
91 | }
92 |
93 | /**
94 | * hook LiveData 改变一些参数
95 | */
96 | @Throws(Exception::class)
97 | private fun hook(observer: Observer) {
98 | val liveDataClass = LiveData::class.java
99 | val mObserversField = liveDataClass.getDeclaredField("mObservers")
100 | mObserversField.isAccessible = true
101 | val mObservers = mObserversField[this]
102 | //获取到mObservers的get方法的反射对象
103 | val get = mObservers.javaClass.getDeclaredMethod("get", Any::class.java)
104 | get.isAccessible = true
105 | val invokeEntry = get.invoke(mObservers, observer)
106 | var observerWrapper: Any? = null
107 | if (invokeEntry != null && invokeEntry is Map.Entry<*, *>) {
108 | observerWrapper = invokeEntry.value
109 | }
110 | if (observerWrapper == null) {
111 | throw NullPointerException("ObserverWrapper不能为空")
112 | }
113 | //获取到ObserverWrapper的类对象
114 | val superclass: Class<*> = observerWrapper.javaClass.superclass
115 | val mLastVersionField = superclass.getDeclaredField("mLastVersion")
116 | mLastVersionField.isAccessible = true
117 | val mVersionField = liveDataClass.getDeclaredField("mVersion")
118 | mVersionField.isAccessible = true
119 | //得到mVersion在当前类中的值
120 | val o = mVersionField[this]
121 | //把它的值给mLastVersion
122 | mLastVersionField[observerWrapper] = o
123 | }
124 | }
125 |
126 | companion object {
127 | private val liveDataBus = LiveEventBus()
128 | fun get(): LiveEventBus {
129 | return liveDataBus
130 | }
131 | }
132 |
133 | init {
134 | mLiveDataMap = HashMap()
135 | }
136 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/utils/UIUtil.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.utils
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.res.Configuration
6 | import android.os.Handler
7 | import android.os.Looper
8 | import android.os.Message
9 |
10 | /**
11 | * Description:主线程UI工具类
12 | * Created by 杜乾 on 2022/8/10 - 15:23.
13 | * E-mail: duqian2010@gmail.com
14 | */
15 | object UIUtil {
16 | private var sMainHandler: Handler? = Handler(Looper.getMainLooper())
17 |
18 | val isOnUiThread: Boolean
19 | get() = Looper.myLooper() == Looper.getMainLooper()
20 |
21 | fun runOnUiThread(runnable: Runnable?, token: Any? = null) {
22 | runOnUiThreadDelay(runnable, 0, token)
23 | }
24 |
25 | fun runOnUiThread(runnable: Runnable?, delayMillis: Long) {
26 | runOnUiThreadDelay(runnable, delayMillis, null)
27 | }
28 |
29 | private fun runOnUiThreadDelay(runnable: Runnable?, delayMillis: Long, token: Any?) {
30 | if (sMainHandler == null) {
31 | sMainHandler = Handler(Looper.getMainLooper())
32 | }
33 | val message = Message.obtain(sMainHandler, runnable)
34 | if (token != null) {
35 | message.what = token.hashCode()
36 | }
37 | sMainHandler?.sendMessageDelayed(message, delayMillis)
38 | }
39 |
40 | fun runOnUiThread(action: Runnable) {
41 | if (isOnUiThread) {
42 | action.run()
43 | } else {
44 | sMainHandler?.post(action)
45 | }
46 | }
47 |
48 | fun runOnUiThreadDelay(action: Runnable, delay: Long) {
49 | sMainHandler?.postDelayed(action, delay)
50 | }
51 |
52 | fun runOnUiThreadAtFront(action: Runnable) {
53 | sMainHandler?.postAtFrontOfQueue(action)
54 | }
55 |
56 | fun removeCallbacks(action: Runnable) {
57 | sMainHandler?.removeCallbacks(action)
58 | }
59 |
60 | @JvmStatic
61 | fun isActivityIllegal(context: Context?): Boolean {
62 | return context !is Activity || context.isFinishing || context.isDestroyed
63 | }
64 |
65 | fun isLandscape(context: Context): Boolean {
66 | var isLand = false
67 | try {
68 | val mConfiguration = context.resources.configuration //获取设置的配置信息
69 | val ori = mConfiguration.orientation //获取屏幕方向
70 | isLand = ori == Configuration.ORIENTATION_LANDSCAPE
71 | } catch (e: Exception) {
72 | e.printStackTrace()
73 | }
74 | return isLand
75 | }
76 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/viewmodel/GlobalViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.viewmodel
2 |
3 | import androidx.lifecycle.AndroidViewModel
4 | import androidx.lifecycle.MutableLiveData
5 | import com.duqian.app.base.BaseApplication
6 |
7 | /**
8 | * Description:App级别的ViewModel
9 | * 直播间数据暴露给直播间外部,使用Application级别的ViewModel
10 | * Created by 杜乾 on 2022/8/11 - 14:02.
11 | * E-mail: duqian2010@gmail.com
12 | */
13 | class GlobalViewModel : AndroidViewModel(BaseApplication.instance) {
14 | //App级别的数据定义在这里,如:
15 | val isLogin = MutableLiveData()
16 | }
17 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/live/viewmodel/RoomGlobalViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.live.viewmodel
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import androidx.lifecycle.ViewModel
5 |
6 | /**
7 | * Description:直播间全局的状态管理,使用MutableLiveData,方便监听变化
8 | * LifecycleOwner为activity(如:LiveRoomActivity),子模块内部非直播间全局的ViewModel,请用fragment作为owner
9 | * 如果直播间的数据要暴露给外部,请定义在GlobalViewModel中。
10 | * Created by 杜乾 on 2022/8/11 - 14:02.
11 | * E-mail: duqian2010@gmail.com
12 | */
13 | class RoomGlobalViewModel : ViewModel() {
14 |
15 | /**
16 | * 房间类型,房间改变,一般都要改变UI
17 | */
18 | val roomType = MutableLiveData()
19 | val roomId = MutableLiveData()
20 | val isPushRoom = MutableLiveData()
21 | val isLandscape = MutableLiveData()
22 | val isRoomLoading = MutableLiveData() //直播间loading
23 |
24 | //拓展直播间全局
25 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/main/MainFragment.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.main
2 |
3 | import android.Manifest
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.duqian.app.live.R
6 | import com.duqian.app.base.BaseApplication
7 | import com.duqian.app.base.BaseFragment
8 | import com.duqian.app.helper.CommonUtils
9 | import com.duqian.app.live.base_comm.RoomConstants
10 | import com.duqian.app.navigator.AppNavigator
11 | import com.duqian.app.navigator.RoomScreens
12 | import dagger.hilt.android.AndroidEntryPoint
13 | import kotlinx.android.synthetic.main.fragment_main.*
14 | import me.ele.uetool.UETool
15 | import pub.devrel.easypermissions.EasyPermissions
16 | import javax.inject.Inject
17 |
18 | /**
19 | * Description:首页MainFragment
20 | *
21 | * Created by 杜乾 on 2022/8/8 - 17:08.
22 | * E-mail: duqian2010@gmail.com
23 | */
24 |
25 | @AndroidEntryPoint
26 | class MainFragment : BaseFragment() {
27 |
28 | companion object {
29 | private const val TAG = "MainFragment"
30 |
31 | fun newInstance() = MainFragment()
32 | }
33 |
34 | private lateinit var viewModel: MainViewModel
35 |
36 | @Inject
37 | lateinit var navigator: AppNavigator
38 |
39 | override fun getLayoutId(): Int {
40 | return R.layout.fragment_main
41 | }
42 |
43 | override fun initData() {
44 |
45 | }
46 |
47 | override fun initView() {
48 | mainContainer.setOnLongClickListener {
49 | val enable: Boolean =
50 | BaseApplication.instance.mmkv?.decodeBool("UEToolEnable", false) ?: false
51 | BaseApplication.instance.mmkv?.encode("UEToolEnable", !enable)
52 | if (enable) UETool.dismissUETMenu() else UETool.showUETMenu()
53 | false
54 | }
55 | }
56 |
57 | override fun initListener() {
58 | tv_go_live.setOnClickListener {
59 | requestLivePermission()
60 | }
61 | tv_live_room.setOnClickListener {
62 | navigator.navigateTo(RoomScreens.LIVE_ROOM)
63 | /*val roomParams = LiveRoomParams(12345, RoomFrom.ROOM_FROM_LIVE)
64 | LiveRoomActivity.startActivity(context, roomParams)*/
65 | }
66 | }
67 |
68 | override fun observerData() {
69 | viewModel = ViewModelProvider(this)[MainViewModel::class.java]
70 | }
71 |
72 | fun startLiveActivity() {
73 | //val roomParams = PushRoomParams(6666, RoomFrom.ROOM_FROM_LIVE)
74 | //PushRoomActivity.startActivity(context, roomParams)
75 | navigator.navigateTo(RoomScreens.PUSH_ROOM)
76 | }
77 |
78 | private fun requestLivePermission() {
79 | if (CommonUtils.checkSelfPermission(
80 | requireActivity(),
81 | Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO
82 | )
83 | ) {
84 | startLiveActivity()
85 | } else {
86 | //ToastUtils.show("no camera permission")
87 | EasyPermissions.requestPermissions(
88 | requireActivity(),
89 | getString(R.string.text_camera_and_audio_rationale),
90 | RoomConstants.REQUEST_CODE_CAMERA_AND_AUDIO,
91 | Manifest.permission.CAMERA,
92 | Manifest.permission.RECORD_AUDIO
93 | )
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/main/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.main
2 |
3 | import androidx.lifecycle.ViewModel
4 |
5 | class MainViewModel : ViewModel() {
6 | // TODO: Implement the ViewModel
7 | }
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/navigator/AppNavigator.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.navigator
2 |
3 | /**
4 | * 直播间房间类型,screens.
5 | */
6 | enum class RoomScreens {
7 | LIVE_ROOM,
8 | PUSH_ROOM,
9 | }
10 |
11 | /**
12 | * Description:定义页面导航
13 | *
14 | * Created by 杜乾 on 2022/8/14 - 13:47.
15 | * E-mail: duqian2010@gmail.com
16 | */
17 | interface AppNavigator {
18 | fun navigateTo(screen: RoomScreens)
19 | }
20 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/navigator/AppNavigatorImpl.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.navigator
2 |
3 | import androidx.fragment.app.FragmentActivity
4 | import com.duqian.app.live.base_comm.RoomFrom
5 | import com.duqian.app.live.liveroom.LiveRoomActivity
6 | import com.duqian.app.live.liveroom.data.LiveRoomParams
7 | import com.duqian.app.live.pushroom.PushRoomActivity
8 | import com.duqian.app.live.pushroom.data.PushRoomParams
9 | import javax.inject.Inject
10 |
11 | /**
12 | * Description:实现房间跳转
13 | *
14 | * Created by 杜乾 on 2022/8/14 - 13:50.
15 | * E-mail: duqian2010@gmail.com
16 | */
17 | class AppNavigatorImpl @Inject constructor(private val activity: FragmentActivity) : AppNavigator {
18 |
19 | override fun navigateTo(screen: RoomScreens) {
20 | when (screen) {
21 | RoomScreens.LIVE_ROOM -> {
22 | //ToastUtils.show("enter live room")
23 | //粘性事件
24 | //LiveEventBus.get().with(LiveEventKey.EVENT_KEY_VIEWER_COUNT, Int::class.java).postValue(68899)
25 |
26 | val roomParams = LiveRoomParams(12345, RoomFrom.ROOM_FROM_LIVE)
27 | LiveRoomActivity.startActivity(activity, roomParams)
28 | }
29 | RoomScreens.PUSH_ROOM -> {
30 | //ToastUtils.show("enter push room")
31 | val roomParams = PushRoomParams(6666, RoomFrom.ROOM_FROM_LIVE)
32 | PushRoomActivity.startActivity(activity, roomParams)
33 | }
34 | }
35 |
36 | /*activity.supportFragmentManager.beginTransaction()
37 | .replace(R.id.main_container, fragment)
38 | .addToBackStack(fragment::class.java.canonicalName)
39 | .commit()*/
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app_live/src/main/java/com/duqian/app/navigator/NavigationModule.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.app.navigator
2 |
3 | import dagger.Binds
4 | import dagger.Module
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.android.components.ActivityComponent
7 |
8 | @InstallIn(ActivityComponent::class)
9 | @Module
10 | abstract class NavigationModule {
11 |
12 | @Binds
13 | abstract fun bindNavigator(impl: AppNavigatorImpl): AppNavigator
14 | }
15 |
--------------------------------------------------------------------------------
/app_live/src/main/res/anim/loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/app_live/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app_live/src/main/res/drawable/shape_live_room_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 | -
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app_live/src/main/res/drawable/shape_live_top_anchor_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app_live/src/main/res/drawable/shape_push_start_live_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
10 |
11 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/activity_live_push_room.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
22 |
23 |
24 |
28 |
29 |
30 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/activity_live_room.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
22 |
26 |
27 |
28 |
32 |
33 |
34 |
38 |
39 |
40 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/dialog_room_gift_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
16 |
17 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/fragment_liveroom_bottom_btns.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
21 |
22 |
23 |
30 |
31 |
39 |
40 |
41 |
49 |
50 |
51 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
19 |
20 |
28 |
29 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/fragment_pushroom_bottom_btns.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
21 |
22 |
29 |
30 |
38 |
39 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/fragment_pushroom_preview.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
22 |
23 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/fragment_room_gift_effect.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/fragment_room_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
13 |
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
37 |
38 |
39 |
43 |
44 |
45 |
49 |
50 |
51 |
55 |
56 |
57 |
61 |
62 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/fragment_room_public_chat.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
23 |
24 |
25 |
39 |
40 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/fragment_room_test_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
18 |
19 |
30 |
31 |
42 |
43 |
44 |
48 |
49 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/include_liveroom_player.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
17 |
22 |
23 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/include_pushroom_preview.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
12 |
13 |
18 |
19 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/include_room_close_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/include_room_common_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
17 |
18 |
29 |
30 |
31 |
43 |
44 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/include_room_public_chat_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app_live/src/main/res/layout/include_room_top_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
17 |
27 |
28 |
37 |
38 |
51 |
52 |
69 |
70 |
71 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/bg_dq_live_splash.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/bg_dq_live_splash.jpeg
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/bg_live_common_btn.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/bg_live_common_btn.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/bg_live_pushroom_go_live.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/bg_live_pushroom_go_live.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/bg_live_room_loading_default.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/bg_live_room_loading_default.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_close_room.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_close_room.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_pushroom_btn_mic_off.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_pushroom_btn_mic_off.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_pushroom_btn_mic_on.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_pushroom_btn_mic_on.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_anchor_follow.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_anchor_follow.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_call.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_call.png
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_chat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_chat.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_coins.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_coins.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_coins_activity.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_coins_activity.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_gift.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_btn_gift.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_hot_num.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_hot_num.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_loading_circle.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxhdpi/ic_live_room_loading_circle.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxxhdpi/bg_room_gift_shop.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxxhdpi/bg_room_gift_shop.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxxhdpi/ic_live_default_head_icon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxxhdpi/ic_live_default_head_icon.webp
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxxhdpi/ic_live_default_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxxhdpi/ic_live_default_icon.png
--------------------------------------------------------------------------------
/app_live/src/main/res/mipmap-xxxhdpi/ic_live_default_icon2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/app_live/src/main/res/mipmap-xxxhdpi/ic_live_default_icon2.png
--------------------------------------------------------------------------------
/app_live/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
--------------------------------------------------------------------------------
/app_live/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app_live/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @color/common_color_5
5 | #ffffff
6 | @color/common_color_5
7 | @color/colorPrimary
8 |
9 |
10 | #B81BEC
11 | #4A1DE7
12 | #FFE34F
13 | #FF5858
14 | #952CFF
15 | #7FFF4A8B
16 | #262628
17 | #909094
18 | #B6B7BC
19 |
20 |
21 | #FFBB86FC
22 | #FF6200EE
23 | #FF3700B3
24 | #FF03DAC5
25 | #FF018786
26 | #00000000
27 | #FF039BE5
28 | #FF01579B
29 | #FF40C4FF
30 | #FF00B0FF
31 | #66000000
32 | #1D1613
33 |
34 |
35 | #FFFFFFFF
36 | #FF000000
37 |
38 |
39 | #E6000000
40 | #cc000000
41 | #B3000000
42 | #99000000
43 | #80000000
44 | #66000000
45 | #4D000000
46 | #33000000
47 | #1A000000
48 |
49 |
50 | #1Affffff
51 | #33ffffff
52 | #4Dffffff
53 | #66ffffff
54 | #80ffffff
55 | #99ffffff
56 | #B3ffffff
57 | #ccffffff
58 | #E6ffffff
59 |
60 |
--------------------------------------------------------------------------------
/app_live/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 35dp
5 |
--------------------------------------------------------------------------------
/app_live/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | DQLive
3 | LiveRoomActivity
4 | Need camera and audio permissions to start live
5 | Need sdcard permissions to play local video
6 |
--------------------------------------------------------------------------------
/app_live/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
11 |
12 |
16 |
17 |
21 |
--------------------------------------------------------------------------------
/app_live/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
16 |
17 |
18 |
19 |
32 |
33 |
40 |
41 |
45 |
--------------------------------------------------------------------------------
/app_live/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app_live/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app_live/src/main/res/xml/file_provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app_live/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | miloji.live
6 |
7 |
8 |
9 |
10 | res.miloji.live
11 | test-api.miloji.live
12 | ws.miloji.live
13 |
14 |
15 |
16 |
17 |
18 | android.bugly.qq.com
19 | api-test.gameschalo.com
20 | log.test.v-mate.mobi
21 | test-api-in.cbrcg.com
22 | test.appnow.store
23 | pay.bmartpay.com
24 | bigfun.xiaoxiangwan.com
25 | res-fq.miloji.live
26 | www.miloji.live
27 |
28 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | dependencies {
4 | classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40'
5 | }
6 | }
7 |
8 | plugins {
9 | id 'com.android.application' version '7.2.1' apply false //4.1.1 -->7.2.1
10 | id 'com.android.library' version '7.2.1' apply false
11 | id 'org.jetbrains.kotlin.android' version '1.6.10' apply false //1.4.31--> 1.6.10
12 | }
13 |
14 | task clean(type: Delete) {
15 | delete rootProject.buildDir
16 | }
--------------------------------------------------------------------------------
/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=-Xmx2048m -Dfile.encoding=UTF-8
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 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | #androidx
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | # Enables namespacing of each library's R class so that its R class includes only the
23 | # resources declared in the library itself and none from the library's dependencies,
24 | # thereby reducing the size of the R class for that library
25 | android.nonTransitiveRClass=true
26 |
27 | # live as library
28 | isLiveModuleLib = false
29 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Aug 09 16:12:41 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
4 | #distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
5 | distributionPath=wrapper/dists
6 | zipStorePath=wrapper/dists
7 | zipStoreBase=GRADLE_USER_HOME
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/lib_livepusher/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lib_livepusher/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-kapt'
5 | }
6 |
7 | android {
8 | compileSdk 32
9 |
10 | defaultConfig {
11 | minSdk 23
12 | targetSdk 31
13 |
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_1_8
25 | targetCompatibility JavaVersion.VERSION_1_8
26 | }
27 | kotlinOptions {
28 | jvmTarget = '1.8'
29 | }
30 | }
31 |
32 | dependencies {
33 |
34 | implementation 'androidx.core:core-ktx:1.6.0'
35 | implementation 'androidx.appcompat:appcompat:1.5.0'
36 |
37 | //Arogra SDK
38 | api 'io.agora.rtc:full-sdk:3.5.3'
39 |
40 | //autoService
41 | kapt "com.google.auto.service:auto-service:1.0-rc6"
42 | implementation "com.google.auto.service:auto-service:1.0-rc6"
43 | }
--------------------------------------------------------------------------------
/lib_livepusher/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/lib_livepusher/consumer-rules.pro
--------------------------------------------------------------------------------
/lib_livepusher/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
--------------------------------------------------------------------------------
/lib_livepusher/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/lib_livepusher/src/main/java/com/duqian/live/pusher/IBasePusher.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.live.pusher
2 |
3 | import android.content.Context
4 | import android.view.ViewGroup
5 | import com.duqian.live.pusher.helper.LifecycleEvent
6 |
7 | /**
8 | * Description:推流相关接口定义
9 | *
10 | * Created by 杜乾 on 2022/8/18 - 15:08.
11 | * E-mail: duqian2010@gmail.com
12 | */
13 | interface IBasePusher {
14 |
15 | /**
16 | * 初始化推流配置
17 | */
18 | fun init(context: Context, videoContainer: ViewGroup): Int
19 |
20 | /**
21 | * 开始摄像头预览
22 | */
23 | fun startPreview(): Int
24 |
25 | /**
26 | * 停止摄像头预览
27 | */
28 | fun stopPreview(): Int
29 |
30 | /**
31 | * 进入房间/频道
32 | */
33 | fun joinChannel(): Int
34 |
35 | /**
36 | * true则停止发送本地媒体流
37 | */
38 | fun muteLocalAudioStream(isMute: Boolean): Int
39 |
40 | /**
41 | * 退出房间/频道
42 | */
43 | fun leaveChannel(): Int
44 |
45 | /**
46 | * 销毁资源
47 | * mRtcEngine.leaveChannel();mRtcEngine.destroy();
48 | */
49 | fun release()
50 |
51 | /**
52 | * 检测声音
53 | */
54 | fun startEchoTest(isTest: Boolean): Int
55 |
56 | /**
57 | * 禁止视频,声音功能
58 | */
59 | fun enableAudioVideo(isVideo: Boolean = false, isEnable: Boolean = true): Int
60 |
61 | /**
62 | * 开启CDN推流
63 | */
64 | fun startPushToCDN(url: String?): Int
65 |
66 | /**
67 | * onResume
68 | */
69 | fun onLifecycleChanged(@LifecycleEvent event: Int)
70 |
71 | /**
72 | * 推流声音
73 | */
74 | fun adjustRecordingSignalVolume(volume: Int): Int
75 |
76 | /**
77 | * 外部设置推流整个过程中的状态,action值越大,流程越后面
78 | */
79 | fun setCallBack(callback: ILiveCallback?)
80 | }
--------------------------------------------------------------------------------
/lib_livepusher/src/main/java/com/duqian/live/pusher/ILiveCallback.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.live.pusher
2 |
3 | /**
4 | * Description:推流相关 回调 定义
5 | *
6 | * Created by 杜乾 on 2022/8/18 - 15:08.
7 | * E-mail: duqian@flatincbr.com
8 | */
9 | interface ILiveCallback {
10 |
11 | /**
12 | * action值越大,流程越后面
13 | */
14 | fun onSuccess(action: Int, uid: Int = 0)
15 |
16 | fun onFailed(errorCode: Int? = 0, errorMsg: String? = "")
17 |
18 | fun onConnectionStateChanged(state: Int, reason: Int) {}
19 | }
20 |
21 | object ResultCode {
22 | const val STATUS_OK = 0 //成功
23 | const val STATUS_FAILED = -1 //失败
24 | }
25 |
--------------------------------------------------------------------------------
/lib_livepusher/src/main/java/com/duqian/live/pusher/helper/CommonState.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.live.pusher.helper
2 |
3 | import androidx.annotation.IntDef
4 |
5 | class CommonState {
6 | }
7 |
8 | /**
9 | * 当前的页面状态
10 | */
11 | @IntDef(value = [LifecycleEvent.ON_RESUME, LifecycleEvent.ON_PAUSE, LifecycleEvent.ON_DESTROY])
12 | @Retention(AnnotationRetention.SOURCE)
13 | annotation class LifecycleEvent {
14 | companion object {
15 | const val ON_RESUME = 0
16 | const val ON_PAUSE = 1
17 | const val ON_DESTROY = 2
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/lib_livepusher/src/main/java/com/duqian/live/pusher/helper/PusherConfigHelper.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.live.pusher.helper
2 |
3 | import io.agora.rtc.video.VideoEncoderConfiguration
4 |
5 | /**
6 | * Description:配置参数转换为声网的配置,先跟目前的1v1通话的配置AvConfig类似
7 | *
8 | * Created by 杜乾 on 2022/8/18 - 22:00.
9 | * E-mail: duqian2010@gmail.com
10 | */
11 | object PusherConfigHelper {
12 |
13 | /**
14 | * 根绝视频分辨率和帧率档位,初始化视频配置
15 | */
16 | fun initVideoEncoderConfiguration(
17 | videoDimensionsLevel: Int,
18 | fpsLevel: Int
19 | ): VideoEncoderConfiguration {
20 | // 档位 1:160x120 2:320x240 3:480x360 4:640x480 5:960x720 6:1280x720
21 | val videoDimensions: VideoEncoderConfiguration.VideoDimensions =
22 | when (videoDimensionsLevel) {
23 | 1 -> VideoEncoderConfiguration.VD_160x120
24 | 2 -> VideoEncoderConfiguration.VD_320x240
25 | 3 -> VideoEncoderConfiguration.VD_480x360
26 | 4 -> VideoEncoderConfiguration.VD_640x480
27 | 5 -> VideoEncoderConfiguration.VD_960x720
28 | 6 -> VideoEncoderConfiguration.VD_1280x720
29 | else -> VideoEncoderConfiguration.VD_640x480
30 | }
31 |
32 | // 档位 1:7帧 2:10帧 3:15帧 4:24帧 5:30帧
33 | val frameFPS: VideoEncoderConfiguration.FRAME_RATE = when (fpsLevel) {
34 | 1 -> VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_7
35 | 2 -> VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_10
36 | 3 -> VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15
37 | 4 -> VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_24
38 | 5 -> VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_30
39 | else -> VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15
40 | }
41 |
42 | return VideoEncoderConfiguration(
43 | videoDimensions,
44 | frameFPS,
45 | VideoEncoderConfiguration.STANDARD_BITRATE,
46 | VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
47 | )
48 | }
49 | }
--------------------------------------------------------------------------------
/lib_mediaplayer/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lib_mediaplayer/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-android'
5 | id 'kotlin-android-extensions'
6 | id 'kotlin-kapt'
7 | }
8 |
9 | android {
10 | compileSdk 32
11 |
12 | defaultConfig {
13 | minSdk 23
14 | targetSdk 31
15 |
16 | consumerProguardFiles "consumer-rules.pro"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility JavaVersion.VERSION_1_8
27 | targetCompatibility JavaVersion.VERSION_1_8
28 | }
29 | kotlinOptions {
30 | jvmTarget = '1.8'
31 | }
32 | }
33 |
34 | def exoVersion = "2.18.1"//"2.14.2"
35 |
36 | dependencies {
37 |
38 | implementation 'androidx.core:core-ktx:1.6.0'
39 | implementation 'androidx.appcompat:appcompat:1.5.0'
40 |
41 | //exo
42 | //api "com.google.android.exoplayer:exoplayer:$exoVersion"
43 | api "com.google.android.exoplayer:exoplayer-core:$exoVersion"
44 | api "com.google.android.exoplayer:exoplayer-common:$exoVersion"
45 | api "com.google.android.exoplayer:exoplayer-ui:$exoVersion"
46 | api "com.google.android.exoplayer:extension-rtmp:$exoVersion"
47 |
48 | //autoService
49 | kapt 'com.google.auto.service:auto-service:1.0.1'
50 | implementation 'com.google.auto.service:auto-service:1.0.1'
51 | }
--------------------------------------------------------------------------------
/lib_mediaplayer/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/lib_mediaplayer/consumer-rules.pro
--------------------------------------------------------------------------------
/lib_mediaplayer/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
--------------------------------------------------------------------------------
/lib_mediaplayer/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/lib_mediaplayer/src/main/java/com/duqian/live/mediaplayer/ILivePlayerService.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.live.mediaplayer
2 |
3 | import android.content.Context
4 | import com.google.android.exoplayer2.ui.PlayerView
5 |
6 | /**
7 | * Description:播放器接口
8 | *
9 | * Created by 杜乾 on 2022/8/12 - 17:15.
10 | * E-mail: duqian@flatincbr.com
11 | */
12 | interface ILivePlayerService {
13 |
14 | fun initPlayer(context: Context, playerView: PlayerView)
15 |
16 | fun startPlay(url: String)
17 |
18 | fun isPlaying(): Boolean
19 |
20 | fun getPlayState(): String
21 |
22 | fun resumePlay()
23 |
24 | fun pausePlay()
25 |
26 | fun stopPlay()
27 |
28 | fun setVolume(audioVolume: Float)
29 |
30 | fun addCallback(callback: PlayerCallback?)
31 |
32 | fun onDestroy()
33 |
34 | }
35 |
36 | /**
37 | * Description:播放器监听
38 | *
39 | * Created by 杜乾 on 2022/8/15 - 07:37.
40 | * E-mail: duqian@flatincbr.com
41 | */
42 | interface PlayerCallback {
43 |
44 | fun onLoadingChanged(isLoading: Boolean)
45 |
46 | fun onPlayerStateChanged(@PlayState playbackState: String)
47 |
48 | /**
49 | * 首帧回调
50 | */
51 | fun onRenderedFirstFrame() {}
52 |
53 | fun onPlayerError(e: Exception?)
54 |
55 | fun onIsPlayingChanged(isPlaying: Boolean) {}
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/lib_mediaplayer/src/main/java/com/duqian/live/mediaplayer/PlayState.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.live.mediaplayer
2 |
3 | import androidx.annotation.StringDef
4 |
5 | /**
6 | * 直播互动的角色类型
7 | */
8 | @StringDef(
9 | value = [PlayState.STATE_IDLE, PlayState.STATE_BUFFERING, PlayState.STATE_READY, PlayState.STATE_ENDED, PlayState.STATE_ERROR]
10 | )
11 | @Retention(AnnotationRetention.SOURCE)
12 | annotation class PlayState {
13 | companion object {
14 | /** The player does not have any media to play. */
15 | const val STATE_IDLE = "STATE_IDLE"
16 |
17 | /**
18 | * The player is not able to immediately play from its current position. This state typically
19 | * occurs when more data needs to be loaded.
20 | */
21 | const val STATE_BUFFERING = "STATE_BUFFERING"
22 |
23 | /**
24 | * The player is able to immediately play from its current position. The player will be playing if
25 | * [.getPlayWhenReady] is true, and paused otherwise.
26 | */
27 | const val STATE_READY = "STATE_READY"
28 |
29 | /** The player has finished playing the media. */
30 | const val STATE_ENDED = "STATE_ENDED"
31 |
32 | /**
33 | * 自定义的错误状态
34 | */
35 | const val STATE_ERROR = "STATE_ERROR"
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/lib_mediaplayer/src/main/java/com/duqian/live/mediaplayer/RtmpLivePlayer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | package com.duqian.live.mediaplayer
3 |
4 | import android.content.Context
5 | import android.net.Uri
6 | import android.util.Log
7 | import android.view.View
8 | import com.google.android.exoplayer2.ExoPlaybackException
9 | import com.google.android.exoplayer2.ExoPlayerFactory
10 | import com.google.android.exoplayer2.Player
11 | import com.google.android.exoplayer2.SimpleExoPlayer
12 | import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory
13 | import com.google.android.exoplayer2.source.ExtractorMediaSource
14 | import com.google.android.exoplayer2.source.MediaSource
15 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection
16 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
17 | import com.google.android.exoplayer2.trackselection.TrackSelection
18 | import com.google.android.exoplayer2.trackselection.TrackSelector
19 | import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
20 | import com.google.android.exoplayer2.ui.PlayerView
21 | import com.google.android.exoplayer2.upstream.DataSource
22 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
23 | import com.google.android.exoplayer2.util.Util
24 | import com.google.auto.service.AutoService
25 | import java.util.*
26 |
27 | */
28 | /**
29 | * Description:简单的拉流播放器,基于ExoPlayer 2.9.6
30 | *
31 | * Created by 杜乾 on 2022/8/12 - 13:38.
32 | * E-mail: duqian2010@gmail.com
33 | *//*
34 |
35 | @AutoService(value = [ILivePlayerService::class])
36 | class RtmpLivePlayer : ILivePlayerService {
37 |
38 | private var player: SimpleExoPlayer? = null//lateinit property player has not been initialized
39 | private lateinit var playerView: PlayerView
40 |
41 | override fun initPlayer(context: Context, playerView: PlayerView, loadingView: View?) {
42 | this.playerView = playerView
43 |
44 | initPlayer(context, playerView)
45 |
46 | player?.also {
47 | it.addListener(object : Player.EventListener {
48 | override fun onLoadingChanged(isLoading: Boolean) {
49 | super.onLoadingChanged(isLoading)
50 | Log.d("dq-player", "isLoading=$isLoading")
51 | //loadingView?.visibility = if (isLoading) View.VISIBLE else View.GONE
52 | mCallback?.onLoadingChanged(isLoading)
53 | }
54 |
55 | override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
56 | super.onPlayerStateChanged(playWhenReady, playbackState)
57 | //playWhenReady=true
58 | //playbackState=1,failed
59 | //playbackState=2,停止播放
60 | //playbackState=3,拉流成功,播放
61 | */
62 | /* if (playbackState == Player.STATE_READY) {
63 | loadingView?.visibility = View.GONE
64 | }*//*
65 |
66 | Log.d(
67 | "dq-player",
68 | "onPlayerStateChanged,playWhenReady=$playWhenReady,playbackState=$playbackState"
69 | )
70 | mCallback?.onPlayerStateChanged(playbackState)
71 | }
72 |
73 | override fun onPlayerError(error: ExoPlaybackException?) {
74 | super.onPlayerError(error)
75 | Log.d("dq-player", "error=$error")
76 | mCallback?.onPlayerError(error)
77 | }
78 | })
79 | }
80 |
81 | }
82 |
83 | private fun initPlayer(context: Context, playerView: PlayerView) {
84 | if (player == null) {
85 | //val bandwidthMeter: BandwidthMeter = DefaultBandwidthMeter()
86 | val videoTrackSelectionFactory: TrackSelection.Factory =
87 | AdaptiveTrackSelection.Factory()
88 | val trackSelector: TrackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
89 |
90 | player = ExoPlayerFactory.newSimpleInstance(context, trackSelector)
91 |
92 | playerView.player = player
93 |
94 | playerView.useController = false
95 | playerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
96 | }
97 | }
98 |
99 | */
100 | /**
101 | * 播放的方法,待完善
102 | *//*
103 |
104 | override fun startPlay(url: String) {
105 | stopPlay()
106 | //val url = "rtmp://examplepull.agoramdn.com/live/duqian"
107 | //val url = "https://examplepull.agoramdn.com/live/duqian.flv"
108 | val videoSource = createMediaSourceByUrl(url)
109 | player?.also {
110 | it.prepare(videoSource)
111 | it.playWhenReady = true
112 | if (url.endsWith("mp4")) {
113 | //it.repeatMode = Player.REPEAT_MODE_ONE //REPEAT_MODE_ALL闪烁
114 | } else {
115 | it.repeatMode = Player.REPEAT_MODE_OFF
116 | }
117 | }
118 | }
119 |
120 | override fun onResume() {
121 |
122 | }
123 |
124 | private fun createMediaSourceByUrl(url: String): MediaSource {
125 | val isRtmpStream = url.lowercase(Locale.getDefault()).startsWith("rtmp://")
126 | val dataSourceFactory: DataSource.Factory =
127 | if (isRtmpStream) {
128 | RtmpDataSourceFactory()
129 | } else {
130 | DefaultDataSourceFactory(
131 | playerView.context,
132 | Util.getUserAgent(playerView.context, "DQLive")
133 | )
134 | }
135 | return ExtractorMediaSource.Factory(dataSourceFactory)
136 | .createMediaSource(Uri.parse(url))
137 | }
138 |
139 | override fun stopPlay() {
140 | player?.stop()
141 | }
142 |
143 | private var mCallback: PlayerCallback? = null
144 |
145 | override fun addCallback(callback: PlayerCallback?) {
146 | this.mCallback = callback
147 | }
148 |
149 | override fun onDestroy() {
150 | player?.stop()
151 | player?.release()
152 | }
153 | }*/
154 |
--------------------------------------------------------------------------------
/nativelib/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/nativelib/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk 32
8 | android.ndkVersion = "21.4.7075529"
9 |
10 | defaultConfig {
11 | minSdk 23
12 | targetSdk 32
13 |
14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
15 | consumerProguardFiles "consumer-rules.pro"
16 | externalNativeBuild {
17 | cmake {
18 | cppFlags ""
19 | //ndk需要编译的目标架构
20 | abiFilters 'armeabi-v7a', 'arm64-v8a'
21 | }
22 | }
23 | }
24 |
25 | buildTypes {
26 | release {
27 | minifyEnabled false
28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
29 | }
30 | }
31 | externalNativeBuild {
32 | cmake {
33 | path "src/main/cpp/CMakeLists.txt"
34 | version "3.18.1"
35 | }
36 | }
37 | compileOptions {
38 | sourceCompatibility JavaVersion.VERSION_1_8
39 | targetCompatibility JavaVersion.VERSION_1_8
40 | }
41 | kotlinOptions {
42 | jvmTarget = '1.8'
43 | }
44 | }
45 |
46 | dependencies {
47 |
48 | /*implementation 'androidx.core:core-ktx:1.6.0'
49 | implementation 'androidx.appcompat:appcompat:1.5.0'
50 | implementation 'com.google.android.material:material:1.12.0'
51 | testImplementation 'junit:junit:4.13.2'
52 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
53 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'*/
54 | }
--------------------------------------------------------------------------------
/nativelib/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/nativelib/consumer-rules.pro
--------------------------------------------------------------------------------
/nativelib/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
--------------------------------------------------------------------------------
/nativelib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/nativelib/src/main/cpp/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | # For more information about using CMake with Android Studio, read the
2 | # documentation: https://d.android.com/studio/projects/add-native-code.html
3 |
4 | # Sets the minimum version of CMake required to build the native library.
5 |
6 | cmake_minimum_required(VERSION 3.18.1)
7 |
8 | # Declares and names the project.
9 |
10 | project("nativelib")
11 |
12 | # Creates and names a library, sets it as either STATIC
13 | # or SHARED, and provides the relative paths to its source code.
14 | # You can define multiple libraries, and CMake builds them for you.
15 | # Gradle automatically packages shared libraries with your APK.
16 |
17 | add_library( # Sets the name of the library.
18 | nativelib
19 |
20 | # Sets the library as a shared library.
21 | SHARED
22 |
23 | # Provides a relative path to your source file(s).
24 | nativelib.cpp )
25 |
26 | # Searches for a specified prebuilt library and stores the path as a
27 | # variable. Because CMake includes system libraries in the search path by
28 | # default, you only need to specify the name of the public NDK library
29 | # you want to add. CMake verifies that the library exists before
30 | # completing its build.
31 |
32 | find_library( # Sets the name of the path variable.
33 | log-lib
34 |
35 | # Specifies the name of the NDK library that
36 | # you want CMake to locate.
37 | log )
38 |
39 | # Specifies libraries CMake should link to your target library. You
40 | # can link multiple libraries, such as libraries you define in this
41 | # build script, prebuilt third-party libraries, or system libraries.
42 |
43 | target_link_libraries( # Specifies the target library.
44 | nativelib
45 |
46 | # Links the target library to the log library
47 | # included in the NDK.
48 | ${log-lib} )
--------------------------------------------------------------------------------
/nativelib/src/main/cpp/nativelib.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | extern "C" JNIEXPORT jstring JNICALL
5 | Java_com_duqian_nativelib_NativeLib_stringFromJNI(
6 | JNIEnv* env,
7 | jobject /* this */) {
8 | std::string hello = "Hello from C++,DQ";
9 | return env->NewStringUTF(hello.c_str());
10 | }
--------------------------------------------------------------------------------
/nativelib/src/main/java/com/duqian/nativelib/NativeLib.kt:
--------------------------------------------------------------------------------
1 | package com.duqian.nativelib
2 |
3 | class NativeLib {
4 |
5 | /**
6 | * A native method that is implemented by the 'nativelib' native library,
7 | * which is packaged with this application.
8 | */
9 | external fun stringFromJNI(): String
10 |
11 | companion object {
12 | // Used to load the 'nativelib' library on application startup.
13 | init {
14 | System.loadLibrary("nativelib")
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/screenshot/DQLive.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/screenshot/DQLive.mp4
--------------------------------------------------------------------------------
/screenshot/DQLive_Anchor.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/screenshot/DQLive_Anchor.jpeg
--------------------------------------------------------------------------------
/screenshot/DQLive_Landscape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/screenshot/DQLive_Landscape.png
--------------------------------------------------------------------------------
/screenshot/DQLive_Main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/screenshot/DQLive_Main.png
--------------------------------------------------------------------------------
/screenshot/DQLive_Room.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/screenshot/DQLive_Room.jpeg
--------------------------------------------------------------------------------
/screenshot/DQLive_Splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/duqian291902259/DQLive/89a38a5854b58489246d663892669f359445b27b/screenshot/DQLive_Splash.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | maven { url "https://maven.aliyun.com/repository/jcenter" }
7 | maven { url "https://maven.aliyun.com/repository/public" }
8 | }
9 | }
10 | dependencyResolutionManagement {
11 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
12 | repositories {
13 | google()
14 | mavenCentral()
15 | maven { url "https://maven.aliyun.com/repository/jcenter" }
16 | maven { url "https://maven.aliyun.com/repository/public" }
17 | maven { url "https://maven.aliyun.com/repository/google" }
18 | maven { url "https://jitpack.io" }
19 | }
20 | }
21 | rootProject.name = "DQLive"
22 | include ':app_live'
23 | include ':lib_mediaplayer'
24 | include ':lib_livepusher'
25 | include ':nativelib'
26 |
--------------------------------------------------------------------------------