├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ ├── card.json
│ ├── dynamic.json
│ ├── hglo1.ogg
│ ├── hglo2.ogg
│ ├── hglo3.ogg
│ ├── hglo4.ogg
│ ├── hglo5.ogg
│ ├── hglo6.ogg
│ ├── hglo7.ogg
│ ├── hglo8.ogg
│ ├── home.json
│ └── user.json
│ ├── java
│ └── com
│ │ └── lzx
│ │ └── musiclib
│ │ ├── Ext.kt
│ │ ├── HomeActivity.kt
│ │ ├── LifecycleUtils.kt
│ │ ├── NotificationReceiver.kt
│ │ ├── SpConstant.kt
│ │ ├── TestApplication.kt
│ │ ├── adapter
│ │ ├── EfficientAdapter.kt
│ │ ├── EfficientAdapterExt.kt
│ │ └── ViewHolderCreator.kt
│ │ ├── base
│ │ └── BaseFragment.kt
│ │ ├── card
│ │ ├── CardActivity.kt
│ │ ├── CardAdapter.kt
│ │ └── CardFragment.kt
│ │ ├── dynamic
│ │ ├── DynamicActivity.kt
│ │ ├── DynamicAdapter.kt
│ │ ├── DynamicDetailActivity.kt
│ │ └── DynamicFragment.kt
│ │ ├── effect
│ │ ├── EffectActivity.kt
│ │ ├── EqualizerBandView.kt
│ │ ├── VerticalSeekBar.java
│ │ └── VerticalSeekBarWrapper.java
│ │ ├── home
│ │ ├── MainActivity.kt
│ │ ├── PlayDetailActivity.kt
│ │ └── TestActivity.kt
│ │ ├── http
│ │ ├── QQMusicApi.kt
│ │ └── RetrofitClient.kt
│ │ ├── user
│ │ ├── UserAdapter.kt
│ │ └── UserInfoActivity.kt
│ │ ├── viewmodel
│ │ └── MusicViewModel.kt
│ │ └── weight
│ │ ├── CustomTabEntity.java
│ │ ├── CustomTabLayout.kt
│ │ ├── DonutProgress.kt
│ │ ├── GalleryItemDecoration.kt
│ │ ├── MomentAudioView.kt
│ │ ├── MsgView.java
│ │ ├── OnTabSelectListener.java
│ │ ├── Spanny.java
│ │ ├── SpectrumDrawView.kt
│ │ ├── SquareImageView.kt
│ │ ├── SquareView.kt
│ │ └── ViewPagerLayoutManager.kt
│ └── res
│ ├── anim
│ ├── popup_bottom_enter.xml
│ └── popup_bottom_exit.xml
│ ├── drawable-xxhdpi
│ ├── afb.png
│ ├── b0c.png
│ ├── b6e.png
│ ├── ba1.png
│ ├── bt_playpage_button_clock_normal_new.png
│ ├── bt_playpage_loop_press.png
│ ├── dy_bg.png
│ ├── gdt_ic_express_pause.png
│ ├── gdt_ic_express_play.png
│ ├── gdt_ic_pause.png
│ ├── gdt_ic_play.png
│ ├── ic_accompaniment.png
│ ├── ic_arrow_drop_down.png
│ ├── ic_close.png
│ ├── ic_danqu.png
│ ├── ic_live_red_playing1.png
│ ├── ic_live_red_playing2.png
│ ├── ic_live_red_playing3.png
│ ├── ic_live_red_playing4.png
│ ├── ic_live_red_playing5.png
│ ├── ic_next_song.png
│ ├── ic_pre_song.png
│ ├── ic_shunji.png
│ ├── ic_shunxu.png
│ ├── ic_songlist.png
│ ├── ic_speed.png
│ ├── ic_user.png
│ ├── ic_variable_speed.png
│ ├── ic_volume.png
│ ├── icon_bar_close.png
│ ├── icon_dynamic_top_next.png
│ ├── icon_dynamic_top_play.png
│ ├── icon_dynamic_top_stop.png
│ ├── icon_dynamic_voice_loading.png
│ ├── moment_audio_view_pause.png
│ ├── moment_audio_view_play.png
│ ├── note_btn_close.png
│ ├── notify_btn_dark_favorite_normal.png
│ ├── notify_btn_dark_lyrics_normal.png
│ ├── notify_btn_dark_next_pressed.png
│ ├── notify_btn_dark_pause_normal.png
│ ├── notify_btn_dark_play_normal.png
│ ├── notify_btn_dark_prev_pressed.png
│ ├── notify_btn_favorite_checked.png
│ ├── notify_btn_light_favorite_normal.png
│ ├── notify_btn_light_lyrics_normal.png
│ ├── notify_btn_light_next_pressed.png
│ ├── notify_btn_light_pause_normal.png
│ ├── notify_btn_light_play_normal.png
│ ├── notify_btn_light_prev_pressed.png
│ ├── notify_btn_lyrics_checked.png
│ ├── recordfinish.png
│ └── rerecord.png
│ ├── drawable
│ ├── anim_playing.xml
│ ├── anim_round_rotate.xml
│ ├── bg_button_36dp.xml
│ ├── bg_equalizer_band_item.xml
│ ├── bg_play_detail.xml
│ ├── bg_spinner.xml
│ ├── ic_singer_more.xml
│ ├── notify_btn_dark_next_selector.xml
│ ├── notify_btn_dark_pause_selector.xml
│ ├── notify_btn_dark_play_selector.xml
│ ├── notify_btn_dark_prev_selector.xml
│ ├── notify_btn_light_next_selector.xml
│ ├── notify_btn_light_pause_selector.xml
│ ├── notify_btn_light_play_selector.xml
│ ├── notify_btn_light_prev_selector.xml
│ ├── progress_equalizer_band.xml
│ ├── seek_bar_pro.xml
│ ├── seek_bar_thumb.xml
│ ├── shape_bg_dialog.xml
│ └── shape_round_black.xml
│ ├── layout
│ ├── activiity_dynamic_detail.xml
│ ├── activity_card.xml
│ ├── activity_dynamic.xml
│ ├── activity_effect.xml
│ ├── activity_home.xml
│ ├── activity_main.xml
│ ├── activity_play_detail.xml
│ ├── activity_test.xml
│ ├── activity_user.xml
│ ├── dialog_song_list.xml
│ ├── fragment_card.xml
│ ├── fragment_dynamic.xml
│ ├── item_card.xml
│ ├── item_dialog_song_list.xml
│ ├── item_dynamic.xml
│ ├── item_equalizer_band.xml
│ ├── item_home_music.xml
│ ├── item_preset.xml
│ ├── item_user.xml
│ ├── layout_moment_audio_view.xml
│ ├── slide_layout_tab.xml
│ ├── view_notify_big_play.xml
│ └── view_notify_play.xml
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-v19
│ └── style.xml
│ ├── values-v21
│ └── style.xml
│ └── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── sliding_tab_attrs.xml
│ ├── strings.xml
│ └── styles.xml
├── art
├── StarrySky流程图.png
├── WechatIMG1.jpeg
├── a4074094959_10.jpg
├── biaoqing.gif
├── logo.jpg
├── notification1.png
├── notification2.png
├── notification3.png
├── notification4.png
├── qq_qun.jpg
├── 成功案例.png
└── 成功案例副本.png
├── build.gradle
├── docs
├── .nojekyll
├── README.md
├── Service相关配置.md
├── SoundPool.md
├── _sidebar.md
├── index.html
├── 介绍.md
├── 全局拦截器.md
├── 其他配置.md
├── 多实例播放器.md
├── 播放相关api介绍.md
├── 缓存.md
├── 通知栏.md
└── 音效相关.md
├── extension-flac2120
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── libs
│ ├── arm64-v8a
│ │ └── libflacJNI.so
│ ├── armeabi-v7a
│ │ └── libflacJNI.so
│ ├── x86
│ │ └── libflacJNI.so
│ └── x86_64
│ │ └── libflacJNI.so
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── lzx
│ └── extension_flac2120
│ ├── FlacBinarySearchSeeker.java
│ ├── FlacDecoder.java
│ ├── FlacDecoderException.java
│ ├── FlacDecoderJni.java
│ ├── FlacExtractor.java
│ ├── FlacLibrary.java
│ └── LibflacAudioRenderer.java
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── starrysky
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
└── main
├── AndroidManifest.xml
├── java
└── com
│ └── lzx
│ └── starrysky
│ ├── AppLifecycleCallback.kt
│ ├── OnPlayerEventListener.kt
│ ├── SongInfo.kt
│ ├── StarrySky.kt
│ ├── StarrySkyInstall.kt
│ ├── StarrySkyPlayer.kt
│ ├── cache
│ ├── ExoCache.kt
│ └── ICache.kt
│ ├── control
│ ├── PlayerControl.kt
│ ├── RepeatMode.kt
│ └── VoiceEffect.kt
│ ├── intercept
│ ├── InterceptorService.kt
│ └── StarrySkyInterceptor.kt
│ ├── manager
│ ├── MediaSessionManager.kt
│ ├── PlaybackManager.kt
│ └── PlaybackStage.kt
│ ├── notification
│ ├── CustomNotification.kt
│ ├── INotification.kt
│ ├── NotificationConfig.kt
│ ├── NotificationManager.kt
│ ├── SystemNotification.kt
│ ├── imageloader
│ │ ├── DefaultImageLoader.kt
│ │ └── ImageLoader.kt
│ └── utils
│ │ ├── NotificationColorUtils.kt
│ │ └── NotificationUtils.kt
│ ├── playback
│ ├── ExoPlayback.kt
│ ├── FocusManager.kt
│ ├── Playback.kt
│ └── SoundPoolPlayback.kt
│ ├── queue
│ ├── MediaQueueManager.kt
│ └── MediaSourceProvider.kt
│ ├── service
│ ├── MusicService.kt
│ ├── MusicServiceBinder.kt
│ └── WifiLockHelper.kt
│ └── utils
│ ├── CommExt.kt
│ ├── KtPreferences.kt
│ ├── MD5.java
│ ├── MainLooper.kt
│ ├── StarrySkyConstant.kt
│ └── TimerTaskManager.kt
└── res
├── drawable-nodpi
└── default_art.png
├── drawable-xxhdpi
├── ic_notification.png
├── ic_pause_white_24dp.png
├── ic_play_arrow_white_24dp.png
├── ic_skip_next_white_24dp.png
└── ic_skip_previous_white_24dp.png
├── values
└── strings.xml
└── xml
├── allowed_media_browser_callers.xml
├── automotive_app_desc.xml
└── network_security_config.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | .idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 lizixian
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 | # A Powerful and Streamline MusicLibrary
2 |
3 | [  ](http://developer.android.com/index.html)
4 | [](https://jitpack.io/#EspoirX/StarrySky)
5 | [  ](http://choosealicense.com/licenses/mit/)
6 |
7 |
8 |
9 |
10 | 打包有问题,先用着v2.6.7或者用源码,暂时没心情维护,大家可自己修改,很简单的。
11 |
12 | # StarrySky
13 |
14 | `StarrySky` `MusicLibrary` `Music` `音频集成`
15 |
16 |
17 | 一个丰富,舒服的音乐播放封装库,针对快速集成音频播放功能,减少大家搬砖的时间,你值得拥有。
18 |
19 | [项目文档](https://espoirx.github.io/StarrySky/#/)
20 |
21 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | apply plugin: 'kotlin-kapt'
5 |
6 | android {
7 | compileSdkVersion 31
8 | defaultConfig {
9 | applicationId "com.lzx.starrysky"
10 | minSdkVersion 16
11 | targetSdkVersion 31
12 | versionCode 1
13 | versionName "1.0"
14 | multiDexEnabled true
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 |
23 | lintOptions {
24 | abortOnError false
25 | }
26 |
27 | compileOptions {
28 | sourceCompatibility 1.8
29 | targetCompatibility 1.8
30 | }
31 |
32 | dependencies {
33 | implementation fileTree(dir: 'libs', include: ['*.jar'])
34 | //noinspection GradleCompatible
35 | implementation 'com.android.support:appcompat-v7:28.0.0'
36 | implementation "com.android.support:design:28.0.0"
37 | implementation 'androidx.constraintlayout:constraintlayout:2.0.3'
38 | implementation 'com.github.bumptech.glide:glide:4.11.0'
39 | implementation "com.squareup.okhttp3:okhttp:4.1.0"
40 |
41 | implementation "com.squareup.retrofit2:retrofit:2.9.0"
42 | implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
43 | implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
44 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
45 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
46 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-beta01"
47 |
48 | implementation project(':starrysky')
49 | // implementation 'com.github.EspoirX:StarrySky:v2.6.3'
50 |
51 | implementation 'com.google.android.exoplayer:exoplayer-hls:2.14.1'
52 | implementation 'com.google.android.exoplayer:extension-rtmp:2.14.1'
53 | implementation 'com.google.android.exoplayer:exoplayer-dash:2.14.1'
54 | implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.14.1'
55 |
56 | implementation 'com.qw:soulpermission:1.3.0'
57 | implementation 'com.danikula:videocache:2.7.1'
58 | implementation 'com.bigkoo:convenientbanner:2.1.5'
59 | implementation 'com.gcssloop.widget:rclayout:1.8.1'
60 | implementation 'com.mikhaellopez:circularimageview:4.2.0'
61 | implementation 'com.noober.background:core:1.6.5'
62 | implementation 'com.afollestad.material-dialogs:core:3.3.0'
63 | implementation 'com.afollestad.material-dialogs:bottomsheets:3.3.0'
64 |
65 | implementation 'com.tencent.bugly:crashreport:3.1.0'
66 | implementation 'com.tencent.bugly:nativecrashreport:3.7.1'
67 |
68 | implementation "com.github.ssseasonnn.RxDownload:rxdownload4:1.0.9"
69 | implementation 'com.github.Lundez:Croller:1.0.3'
70 | }
71 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | # ServiceLoader support
23 | -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
24 | -keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}
25 | -keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {}
26 | -keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {}
27 |
28 | # Most of volatile fields are updated with AFU and should not be mangled
29 | -keepclassmembernames class kotlinx.** {
30 | volatile ;
31 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
18 |
19 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/assets/hglo1.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/assets/hglo1.ogg
--------------------------------------------------------------------------------
/app/src/main/assets/hglo2.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/assets/hglo2.ogg
--------------------------------------------------------------------------------
/app/src/main/assets/hglo3.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/assets/hglo3.ogg
--------------------------------------------------------------------------------
/app/src/main/assets/hglo4.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/assets/hglo4.ogg
--------------------------------------------------------------------------------
/app/src/main/assets/hglo5.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/assets/hglo5.ogg
--------------------------------------------------------------------------------
/app/src/main/assets/hglo6.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/assets/hglo6.ogg
--------------------------------------------------------------------------------
/app/src/main/assets/hglo7.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/assets/hglo7.ogg
--------------------------------------------------------------------------------
/app/src/main/assets/hglo8.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/assets/hglo8.ogg
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/HomeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import com.lzx.musiclib.home.MainActivity
6 | import com.lzx.musiclib.home.TestActivity
7 | import com.lzx.starrysky.StarrySky
8 | import kotlinx.android.synthetic.main.activity_home.btn1
9 | import kotlinx.android.synthetic.main.activity_home.btn2
10 |
11 | class HomeActivity : AppCompatActivity() {
12 | override fun onCreate(savedInstanceState: Bundle?) {
13 | super.onCreate(savedInstanceState)
14 | setContentView(R.layout.activity_home)
15 | btn1.setOnClickListener { navigationTo() }
16 | btn2.setOnClickListener { navigationTo() }
17 | }
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/LifecycleUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib
2 |
3 | import android.content.Context
4 | import androidx.lifecycle.LifecycleObserver
5 | import androidx.lifecycle.LifecycleOwner
6 |
7 | object LifecycleUtils {
8 | /**
9 | * 添加观察
10 | */
11 | fun addObserver(context: Context?, observer: LifecycleObserver?) {
12 | if (context is LifecycleOwner) {
13 | val owner = context as LifecycleOwner?
14 | owner?.lifecycle?.addObserver(observer!!)
15 | }
16 | }
17 |
18 | /**
19 | * 移除订阅
20 | */
21 | fun removeObserver(context: Context?, observer: LifecycleObserver?) {
22 | if (context is LifecycleOwner) {
23 | val owner = context as LifecycleOwner?
24 | owner?.lifecycle?.removeObserver(observer!!)
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/NotificationReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import com.lzx.starrysky.SongInfo
7 | import com.lzx.starrysky.StarrySky
8 | import com.lzx.starrysky.utils.getTargetClass
9 |
10 |
11 | /**
12 | * 处理通知栏点击的广播
13 | */
14 | class NotificationReceiver : BroadcastReceiver() {
15 | override fun onReceive(context: Context?, intent: Intent?) {
16 |
17 | //songInfo是当前播放的音频信息
18 | val songInfo = intent?.getParcelableExtra("songInfo")
19 |
20 | //bundleInfo是你在配置通知栏的那个bundle,里面可以拿到你自定义的参数
21 | val bundleInfo = intent?.getBundleExtra("bundleInfo")
22 | val targetClass = bundleInfo?.getString("targetClass")?.getTargetClass()
23 |
24 | if (StarrySky.getActivityStack().isNullOrEmpty()) {
25 | val mainIntent = Intent(context, HomeActivity::class.java)
26 | mainIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
27 |
28 | val targetIntent = Intent(context, targetClass)
29 | targetIntent.putExtra("songId", songInfo?.songId)
30 |
31 | val intents = arrayOf(mainIntent, targetIntent)
32 | context?.startActivities(intents)
33 | } else {
34 | val targetIntent = Intent(context, targetClass)
35 | targetIntent.putExtra("songId", songInfo?.songId)
36 | targetIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
37 | context?.startActivity(targetIntent)
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/SpConstant.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib
2 |
3 | import com.lzx.starrysky.utils.KtPreferences
4 |
5 | object SpConstant : KtPreferences() {
6 | var HAS_PERMISSION by booleanPref()
7 | var KEY_TOKEN by stringPref()
8 | var KEY_EXPIRES by stringPref()
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/base/BaseFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.base
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import androidx.annotation.IdRes
10 | import androidx.fragment.app.Fragment
11 |
12 | abstract class BaseFragment : Fragment() {
13 |
14 | var mContext: Context? = null
15 | var mActivity: Activity? = null
16 |
17 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
18 | val view = inflater.inflate(getResourceId(), container, false)
19 | mActivity = activity
20 | mContext = mActivity
21 | return view
22 | }
23 |
24 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
25 | super.onViewCreated(view, savedInstanceState)
26 | initView(view)
27 | }
28 |
29 |
30 | open fun findViewById(@IdRes id: Int): View? {
31 | return view?.findViewById(id)
32 | }
33 |
34 | override fun onAttach(context: Context) {
35 | super.onAttach(context)
36 | mActivity = context as Activity
37 | mContext = context
38 | }
39 |
40 | override fun onDetach() {
41 | mActivity = null
42 | super.onDetach()
43 | }
44 |
45 | override fun onDestroyView() {
46 | unInitView()
47 | super.onDestroyView()
48 | }
49 |
50 | abstract fun getResourceId(): Int
51 |
52 | abstract fun initView(view: View?)
53 |
54 | abstract fun unInitView()
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/card/CardActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.card
2 |
3 | import android.os.Bundle
4 | import android.view.ViewGroup
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.fragment.app.Fragment
7 | import androidx.fragment.app.FragmentManager
8 | import androidx.fragment.app.FragmentStatePagerAdapter
9 | import com.lzx.musiclib.R
10 | import com.lzx.starrysky.StarrySky
11 | import com.lzx.starrysky.utils.orDef
12 | import kotlinx.android.synthetic.main.activity_card.tabLayout
13 | import kotlinx.android.synthetic.main.activity_card.viewpager
14 |
15 | class CardActivity : AppCompatActivity() {
16 |
17 | private var categoryList = mutableListOf()
18 | private var isStopByOnPause = false
19 | private var adapter: CardCategoryAdapter? = null
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_card)
24 |
25 | StarrySky.closeNotification()
26 | StarrySky.setIsOpenNotification(false)
27 |
28 | categoryList.add(CardCategory("card1", "推荐"))
29 | categoryList.add(CardCategory("card2", "流行电音"))
30 | categoryList.add(CardCategory("card3", "情感陪伴"))
31 | categoryList.add(CardCategory("card4", "动感地带"))
32 | categoryList.add(CardCategory("card5", "劲歌金曲"))
33 |
34 | adapter = CardCategoryAdapter(supportFragmentManager, categoryList)
35 | viewpager.removeAllViews()
36 | viewpager.removeAllViewsInLayout()
37 | viewpager.adapter = adapter
38 | tabLayout.setViewPager(viewpager)
39 | }
40 |
41 | override fun onResume() {
42 | super.onResume()
43 | if (isStopByOnPause) {
44 | val pos = adapter?.currFragment?.curPlayPos.orDef()
45 | adapter?.currFragment?.playCurVoice(pos)
46 | isStopByOnPause = false
47 | }
48 | }
49 |
50 | override fun onPause() {
51 | super.onPause()
52 | isStopByOnPause = true
53 | }
54 | }
55 |
56 | class CardCategoryAdapter(
57 | fm: FragmentManager?,
58 | private var categoryList: MutableList
59 | ) : FragmentStatePagerAdapter(fm!!) {
60 |
61 | private val fragmentMap = hashMapOf()
62 |
63 | override fun getItem(position: Int): Fragment {
64 | val category = categoryList[position]
65 | if (fragmentMap[category.cardType] != null) {
66 | return fragmentMap[category.cardType]!!
67 | }
68 | val fragment = CardFragment.newInstance(category.cardType, category.cardTitle)
69 | fragmentMap[category.cardType] = fragment
70 | return fragment
71 | }
72 |
73 | override fun getCount(): Int = categoryList.size
74 |
75 | override fun getPageTitle(position: Int): CharSequence? {
76 | return categoryList[position].cardTitle
77 | }
78 |
79 | override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
80 | super.destroyItem(container, position, `object`)
81 | val category = categoryList[position]
82 | if (fragmentMap[category.cardType] != null) {
83 | fragmentMap.remove(category.cardType)
84 | }
85 | }
86 |
87 | var currFragment: CardFragment? = null
88 |
89 | override fun setPrimaryItem(container: ViewGroup, position: Int, obj: Any) {
90 | if (obj is CardFragment) {
91 | currFragment = obj
92 | }
93 | super.setPrimaryItem(container, position, obj)
94 | }
95 | }
96 |
97 |
98 | data class CardCategory(var cardType: String, var cardTitle: String)
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/card/CardAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.card
2 |
3 | import android.app.Activity
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.gcssloop.widget.RCImageView
10 | import com.lzx.musiclib.R
11 | import com.lzx.musiclib.dynamic.DynamicDetailActivity
12 | import com.lzx.musiclib.loadImage
13 | import com.lzx.musiclib.navigationTo
14 | import com.lzx.musiclib.user.UserInfoActivity
15 | import com.lzx.starrysky.SongInfo
16 | import com.lzx.starrysky.StarrySky
17 |
18 | class CardAdapter(private val context: Activity?) :
19 | RecyclerView.Adapter() {
20 |
21 | private val list = mutableListOf()
22 |
23 | fun submitList(list: MutableList, isRefresh: Boolean) {
24 | if (isRefresh) {
25 | this.list.clear()
26 | }
27 | this.list.addAll(list)
28 | notifyDataSetChanged()
29 | }
30 |
31 | fun getList() = list
32 |
33 | fun getItem(position: Int): SongInfo? = list.getOrNull(position)
34 |
35 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
36 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_card, parent, false)
37 | return CardHolder(view)
38 | }
39 |
40 | override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
41 | val info = list.getOrNull(position)
42 | val cardHolder = holder as CardHolder
43 | cardHolder.cover.loadImage(info?.songCover)
44 | cardHolder.headerImg.loadImage("https://i2.gqxz.com/uploads/202009/14/200914110924764.jpg")
45 | cardHolder.songName.text = info?.songName
46 | cardHolder.singer.text = info?.artist
47 | cardHolder.itemView.setOnClickListener {
48 | context?.navigationTo("songInfo" to info, "from" to "card")
49 | }
50 | cardHolder.headerImg.setOnClickListener {
51 | StarrySky.with().stopMusic()
52 | context?.navigationTo()
53 | }
54 | }
55 |
56 | class CardHolder(holder: View) : RecyclerView.ViewHolder(holder) {
57 | val cover: RCImageView = holder.findViewById(R.id.cover)
58 | val headerImg: RCImageView = holder.findViewById(R.id.headerImg)
59 | val songName: TextView = holder.findViewById(R.id.songName)
60 | val singer: TextView = holder.findViewById(R.id.singer)
61 |
62 | }
63 |
64 | override fun getItemCount(): Int = list.size
65 | }
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/card/CardFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.card
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.lzx.musiclib.R
6 | import com.lzx.musiclib.base.BaseFragment
7 | import com.lzx.musiclib.getSelfViewModel
8 | import com.lzx.musiclib.showToast
9 | import com.lzx.musiclib.viewmodel.MusicViewModel
10 | import com.lzx.musiclib.weight.GalleryItemDecoration
11 | import com.lzx.musiclib.weight.OnViewPagerListener
12 | import com.lzx.musiclib.weight.ViewPagerLayoutManager
13 | import com.lzx.starrysky.StarrySky
14 | import com.lzx.starrysky.manager.PlaybackStage
15 | import com.lzx.starrysky.utils.orDef
16 | import kotlinx.android.synthetic.main.fragment_card.recycleView
17 |
18 | class CardFragment : BaseFragment() {
19 | override fun getResourceId(): Int = R.layout.fragment_card
20 | private var viewModel: MusicViewModel? = null
21 |
22 | companion object {
23 | fun newInstance(cardType: String, cardName: String) = CardFragment().apply {
24 | arguments = Bundle().apply {
25 | putString("cardType", cardType)
26 | putString("cardName", cardName)
27 | }
28 | }
29 | }
30 |
31 | private var cardType: String? = null
32 | private var cardName: String? = null
33 | private var linearLayoutManager: ViewPagerLayoutManager? = null
34 | private var cardAdapter: CardAdapter? = null
35 | var curPlayPos = 0
36 | private var isVisibleToUser: Boolean = false
37 |
38 | override fun initView(view: View?) {
39 | cardType = arguments?.getString("cardType")
40 | cardName = arguments?.getString("cardName")
41 | viewModel = getSelfViewModel {
42 | cardLiveData.observe(this@CardFragment, {
43 | cardAdapter?.submitList(it, true)
44 | })
45 | }
46 | initRecycleView()
47 | viewModel?.getCardMusicList(cardType)
48 | StarrySky.with().playbackState().observe(this, {
49 | if (it.stage == PlaybackStage.IDLE && !it.isStop) {
50 | //重播
51 | playCurVoice(curPlayPos)
52 | }
53 | })
54 | }
55 |
56 | override fun setUserVisibleHint(isVisibleToUser: Boolean) {
57 | super.setUserVisibleHint(isVisibleToUser)
58 | this.isVisibleToUser = isVisibleToUser
59 | if (isVisibleToUser) {
60 | val position = linearLayoutManager?.getCurrPosition().orDef()
61 | if (position >= 0 && position <= cardAdapter?.getList()?.lastIndex ?: 0) {
62 | curPlayPos = position
63 | }
64 | playCurVoice(curPlayPos)
65 | }
66 | }
67 |
68 | private fun initRecycleView() {
69 | linearLayoutManager = ViewPagerLayoutManager(activity)
70 | linearLayoutManager?.recycleChildrenOnDetach = true
71 | recycleView.layoutManager = linearLayoutManager
72 | recycleView.addItemDecoration(GalleryItemDecoration())
73 | recycleView.adapter = CardAdapter(activity).also { cardAdapter = it }
74 | linearLayoutManager?.setOnViewPagerListener(object : OnViewPagerListener {
75 | override fun onInitComplete() {
76 | val position = linearLayoutManager?.getCurrPosition().orDef()
77 | if (position >= 0 && position <= cardAdapter?.getList()?.lastIndex ?: 0) {
78 | curPlayPos = position
79 | }
80 | playCurVoice(curPlayPos)
81 | }
82 |
83 | override fun onPageRelease(isNext: Boolean, position: Int) {
84 |
85 | }
86 |
87 | override fun onPageSelected(position: Int, isBottom: Boolean) {
88 | if (curPlayPos == position) return
89 | playCurVoice(position)
90 | }
91 | })
92 | }
93 |
94 | fun playCurVoice(position: Int) {
95 | if (!isVisibleToUser) return
96 | curPlayPos = position
97 | val songInfo = cardAdapter?.getItem(position) ?: return
98 | activity?.showToast("当前播放:" + songInfo.songName)
99 | StarrySky.with()
100 | .skipMediaQueue(true)
101 | .playMusicByInfo(songInfo)
102 | }
103 |
104 | override fun unInitView() {
105 | }
106 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/dynamic/DynamicAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.dynamic
2 |
3 | import android.content.Context
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.gcssloop.widget.RCImageView
10 | import com.lzx.musiclib.R
11 | import com.lzx.musiclib.loadImage
12 | import com.lzx.musiclib.navigationTo
13 | import com.lzx.musiclib.user.UserInfoActivity
14 | import com.lzx.musiclib.weight.MomentAudioView
15 | import com.lzx.starrysky.SongInfo
16 | import com.lzx.starrysky.StarrySky
17 |
18 | class DynamicAdapter(val context: Context?) : RecyclerView.Adapter() {
19 |
20 | private val list = mutableListOf()
21 | var listener: OnItemClickListener? = null
22 |
23 | fun submitList(list: MutableList, isRefresh: Boolean) {
24 | if (isRefresh) {
25 | this.list.clear()
26 | }
27 | this.list.addAll(list)
28 | notifyDataSetChanged()
29 | }
30 |
31 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DynamicHolder {
32 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_dynamic, parent, false)
33 | return DynamicHolder(view)
34 | }
35 |
36 | override fun onBindViewHolder(holder: DynamicHolder, position: Int) {
37 | val info = list[position]
38 | holder.userHeader.loadImage(info.songCover)
39 | holder.nickname.text = info.artist
40 | holder.audioView.setVoiceInfo(info)
41 |
42 | val isPlaying = StarrySky.with().isCurrMusicIsPlaying(info.songId)
43 | holder.audioView.setUIState(isPlaying)
44 | holder.audioView.setOnClickListener {
45 | listener?.itemClick(list, position)
46 | }
47 | holder.desc.setOnClickListener {
48 | StarrySky.with().playMusic(list, position)
49 | context?.navigationTo("songInfo" to info, "from" to "dynamic")
50 | }
51 | holder.userHeader.setOnClickListener {
52 | StarrySky.with().stopMusic()
53 | context?.navigationTo()
54 | }
55 | }
56 |
57 | override fun getItemCount(): Int = list.size
58 |
59 | class DynamicHolder(holder: View) : RecyclerView.ViewHolder(holder) {
60 | val userHeader: RCImageView = holder.findViewById(R.id.userHeader)
61 | val nickname: TextView = holder.findViewById(R.id.nickname)
62 | val desc: TextView = holder.findViewById(R.id.desc)
63 | val audioView: MomentAudioView = holder.findViewById(R.id.audioView)
64 | }
65 |
66 | interface OnItemClickListener {
67 | fun itemClick(list: MutableList, position: Int)
68 | }
69 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/dynamic/DynamicDetailActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.dynamic
2 |
3 | import android.animation.Animator
4 | import android.animation.AnimatorListenerAdapter
5 | import android.animation.ObjectAnimator
6 | import android.annotation.SuppressLint
7 | import android.os.Bundle
8 | import android.view.animation.LinearInterpolator
9 | import android.widget.SeekBar
10 | import androidx.appcompat.app.AppCompatActivity
11 | import com.lzx.musiclib.R
12 | import com.lzx.musiclib.loadImage
13 | import com.lzx.starrysky.OnPlayProgressListener
14 | import com.lzx.starrysky.SongInfo
15 | import com.lzx.starrysky.StarrySky
16 | import com.lzx.starrysky.manager.PlaybackStage
17 | import com.lzx.starrysky.utils.formatTime
18 | import kotlinx.android.synthetic.main.activiity_dynamic_detail.cover
19 | import kotlinx.android.synthetic.main.activiity_dynamic_detail.progressText
20 | import kotlinx.android.synthetic.main.activiity_dynamic_detail.seekBar
21 | import kotlinx.android.synthetic.main.activiity_dynamic_detail.songName
22 | import kotlinx.android.synthetic.main.activiity_dynamic_detail.timeText
23 |
24 | class DynamicDetailActivity : AppCompatActivity() {
25 |
26 | private var from: String? = null
27 | private var songInfo: SongInfo? = null
28 | private var rotationAnim: ObjectAnimator? = null
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 | setContentView(R.layout.activiity_dynamic_detail)
33 |
34 | from = intent.getStringExtra("from")
35 | songInfo = intent.getParcelableExtra("songInfo")
36 |
37 | if (from == "dynamic") {
38 | StarrySky.closeNotification()
39 | }
40 |
41 | cover.loadImage(songInfo?.songCover)
42 | songName.text = songInfo?.songName
43 |
44 | rotationAnim = ObjectAnimator.ofFloat(cover, "rotation", 0f, 359f)
45 | rotationAnim?.interpolator = LinearInterpolator()
46 | rotationAnim?.duration = 20000
47 | rotationAnim?.addListener(object : AnimatorListenerAdapter() {
48 | override fun onAnimationEnd(animation: Animator?) {
49 | super.onAnimationEnd(animation)
50 | rotationAnim?.start()
51 | }
52 | })
53 | rotationAnim?.start()
54 |
55 | StarrySky.with().playbackState().observe(this, {
56 | if (it.stage == PlaybackStage.IDLE && !it.isStop) {
57 | //重播
58 | StarrySky.with()
59 | .skipMediaQueue(true)
60 | .playMusicByInfo(songInfo)
61 | }
62 | })
63 | StarrySky.with().setOnPlayProgressListener(object : OnPlayProgressListener {
64 | @SuppressLint("SetTextI18n")
65 | override fun onPlayProgress(currPos: Long, duration: Long) {
66 | if (seekBar.max.toLong() != duration) {
67 | seekBar.max = duration.toInt()
68 | }
69 | seekBar.progress = currPos.toInt()
70 | progressText.text = currPos.formatTime()
71 | timeText.text = " / " + duration.formatTime()
72 | }
73 | })
74 | //进度SeekBar
75 | seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
76 | override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
77 | override fun onStartTrackingTouch(seekBar: SeekBar) {}
78 | override fun onStopTrackingTouch(seekBar: SeekBar) {
79 | StarrySky.with().seekTo(seekBar.progress.toLong(), true)
80 | }
81 | })
82 | }
83 |
84 | override fun onDestroy() {
85 | super.onDestroy()
86 | rotationAnim?.cancel()
87 | rotationAnim?.removeAllListeners()
88 | rotationAnim = null
89 | }
90 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/dynamic/DynamicFragment.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.dynamic
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import com.lzx.musiclib.R
7 | import com.lzx.musiclib.base.BaseFragment
8 | import com.lzx.musiclib.getSelfViewModel
9 | import com.lzx.musiclib.viewmodel.MusicViewModel
10 | import com.lzx.starrysky.SongInfo
11 | import com.lzx.starrysky.StarrySky
12 | import kotlinx.android.synthetic.main.fragment_card.recycleView
13 |
14 | class DynamicFragment : BaseFragment() {
15 | companion object {
16 | fun newInstance(type: String) = DynamicFragment().apply {
17 | arguments = Bundle().apply {
18 | putString("type", type)
19 | }
20 | }
21 | }
22 |
23 | override fun getResourceId(): Int = R.layout.fragment_card
24 |
25 | private var dynamicAdapter: DynamicAdapter? = null
26 | private var viewModel: MusicViewModel? = null
27 | private var type: String? = null
28 |
29 | override fun initView(view: View?) {
30 | type = arguments?.getString("type")
31 | recycleView.layoutManager = LinearLayoutManager(activity)
32 | recycleView.adapter = DynamicAdapter(context).also { dynamicAdapter = it }
33 |
34 | viewModel = getSelfViewModel {
35 | dynamicLiveData.observe(this@DynamicFragment, {
36 | dynamicAdapter?.submitList(it, true)
37 | })
38 | }
39 |
40 | viewModel?.getDynamicMusicList(type)
41 |
42 | StarrySky.with().playbackState().observe(this, {
43 | dynamicAdapter?.notifyDataSetChanged()
44 | })
45 |
46 | dynamicAdapter?.listener = object : DynamicAdapter.OnItemClickListener {
47 | override fun itemClick(list: MutableList, position: Int) {
48 | (activity as DynamicActivity).showVoiceBar()
49 | val info = list[position]
50 | if (StarrySky.with().isCurrMusicIsPlaying(info.songId)) {
51 | StarrySky.with().pauseMusic()
52 | } else {
53 | StarrySky.with().playMusic(list, position)
54 | }
55 | }
56 |
57 | }
58 | }
59 |
60 | override fun unInitView() {
61 | }
62 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/http/QQMusicApi.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.http
2 |
3 | import okhttp3.ResponseBody
4 | import retrofit2.http.GET
5 | import retrofit2.http.Headers
6 | import retrofit2.http.Query
7 |
8 | interface QQMusicApi {
9 | companion object {
10 | const val BASE_URL = "https://api.qq.jsososo.com/"
11 | // https://api.qq.jsososo.com/songlist?id=2429907335
12 | }
13 |
14 | @Headers("User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows 7)")
15 | @GET("song/urls")
16 | suspend fun getMusicUrl(@Query("id") id: String): ResponseBody
17 |
18 | @Headers("User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows 7)")
19 | @GET("songlist")
20 | suspend fun getQQMusicSongList(@Query("id") id: String): ResponseBody
21 |
22 |
23 | @Headers("User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows 7)")
24 | @GET("lzxTreasureBox")
25 | suspend fun getGithubHtml(): ResponseBody
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/http/RetrofitClient.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.http
2 |
3 | import okhttp3.OkHttpClient
4 | import okhttp3.logging.HttpLoggingInterceptor
5 | import retrofit2.Retrofit
6 | import retrofit2.converter.gson.GsonConverterFactory
7 | import java.util.concurrent.TimeUnit
8 |
9 | object RetrofitClient {
10 | private val client: OkHttpClient
11 | get() {
12 | val builder = OkHttpClient.Builder()
13 | builder.connectTimeout(5L, TimeUnit.SECONDS)
14 | val interceptor = HttpLoggingInterceptor()
15 | interceptor.level = HttpLoggingInterceptor.Level.BODY
16 | builder.addInterceptor(interceptor)
17 | return builder.build()
18 | }
19 |
20 | fun getService(serviceClass: Class, baseUrl: String = QQMusicApi.BASE_URL): S {
21 | return Retrofit.Builder()
22 | .client(client)
23 | .baseUrl(baseUrl)
24 | .addConverterFactory(GsonConverterFactory.create())
25 | .build()
26 | .create(serviceClass)
27 | }
28 |
29 | fun getQQMusic(): QQMusicApi {
30 | return getService(QQMusicApi::class.java, QQMusicApi.BASE_URL)
31 | }
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/user/UserAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.user
2 |
3 | import android.annotation.SuppressLint
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.RecyclerView
9 | import com.lzx.musiclib.R
10 | import com.lzx.starrysky.SongInfo
11 | import com.lzx.starrysky.StarrySky
12 | import com.lzx.starrysky.utils.formatTime
13 |
14 | class UserAdapter : RecyclerView.Adapter() {
15 |
16 | private val list = mutableListOf()
17 |
18 | var currPlayingIndex = 0
19 |
20 | fun submitList(list: MutableList, isRefresh: Boolean) {
21 | if (isRefresh) {
22 | this.list.clear()
23 | }
24 | this.list.addAll(list)
25 | notifyDataSetChanged()
26 | }
27 |
28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserHolder {
29 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false)
30 | return UserHolder(view)
31 | }
32 |
33 | override fun onBindViewHolder(holder: UserHolder, position: Int) {
34 | val info = list[position]
35 | holder.musicName.text = info.songName
36 | holder.musicSinger.text = info.artist
37 |
38 | holder.itemView.setOnClickListener {
39 | currPlayingIndex = position
40 | StarrySky.with().playMusic(list, position)
41 | }
42 | }
43 |
44 | @SuppressLint("SetTextI18n")
45 | override fun onBindViewHolder(holder: UserHolder, position: Int, payloads: MutableList) {
46 | super.onBindViewHolder(holder, position, payloads)
47 | val any = payloads.getOrNull(0) ?: return
48 | val info = list[position]
49 | val playingId = StarrySky.with().getNowPlayingSongId()
50 | if (info.songId != playingId) return
51 | val pair = any as Pair<*, *>
52 | val currPos = pair.first as Long
53 | val duration = pair.second as Long
54 | holder.time.text = currPos.formatTime() + " / " + duration.formatTime()
55 | }
56 |
57 | override fun getItemCount(): Int = list.size
58 |
59 | class UserHolder(holder: View) : RecyclerView.ViewHolder(holder) {
60 | val musicName: TextView = holder.findViewById(R.id.musicName)
61 | val musicSinger: TextView = holder.findViewById(R.id.musicSinger)
62 | val time: TextView = holder.findViewById(R.id.time)
63 | }
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/user/UserInfoActivity.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.user
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.recyclerview.widget.LinearLayoutManager
6 | import com.lzx.musiclib.R
7 | import com.lzx.musiclib.getSelfViewModel
8 | import com.lzx.musiclib.loadImage
9 | import com.lzx.musiclib.viewmodel.MusicViewModel
10 | import com.lzx.starrysky.OnPlayProgressListener
11 | import com.lzx.starrysky.StarrySky
12 | import com.lzx.starrysky.manager.PlaybackStage
13 | import com.lzx.starrysky.utils.orDef
14 | import kotlinx.android.synthetic.main.activity_user.bgImage
15 | import kotlinx.android.synthetic.main.activity_user.recycleView
16 |
17 | class UserInfoActivity : AppCompatActivity() {
18 |
19 | private var userAdapter: UserAdapter? = null
20 | private var viewModel: MusicViewModel? = null
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContentView(R.layout.activity_user)
25 |
26 | bgImage.loadImage("https://up.zhuoku.org/pic/ba/5b/a0/ba5ba00c78aafbd57ba5021615b46d8a.jpg")
27 |
28 | recycleView.layoutManager = LinearLayoutManager(this)
29 | recycleView.adapter = UserAdapter().also { userAdapter = it }
30 |
31 | viewModel = getSelfViewModel {
32 | val list = getUserMusicList()
33 | userAdapter?.submitList(list, true)
34 | }
35 |
36 | StarrySky.with().playbackState().observe(this, {
37 | when (it.stage) {
38 | PlaybackStage.PLAYING -> {
39 | userAdapter?.currPlayingIndex = StarrySky.with().getNowPlayingIndex()
40 | userAdapter?.notifyDataSetChanged()
41 | }
42 | }
43 | })
44 |
45 | StarrySky.with().setOnPlayProgressListener(object : OnPlayProgressListener {
46 | override fun onPlayProgress(currPos: Long, duration: Long) {
47 | userAdapter?.notifyItemChanged(userAdapter?.currPlayingIndex.orDef(), Pair(currPos, duration))
48 | }
49 | })
50 | }
51 |
52 | override fun onDestroy() {
53 | super.onDestroy()
54 | StarrySky.with().stopMusic()
55 | }
56 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/weight/CustomTabEntity.java:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.weight;
2 |
3 | import androidx.annotation.DrawableRes;
4 |
5 | public interface CustomTabEntity {
6 | String getTabTitle();
7 |
8 | @DrawableRes
9 | int getTabSelectedIcon();
10 |
11 | @DrawableRes
12 | int getTabUnselectedIcon();
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/weight/GalleryItemDecoration.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.weight
2 |
3 | import android.graphics.Rect
4 | import android.view.View
5 | import androidx.recyclerview.widget.RecyclerView
6 | import com.lzx.musiclib.dp
7 | import com.lzx.starrysky.utils.orDef
8 |
9 |
10 | class GalleryItemDecoration() : RecyclerView.ItemDecoration() {
11 |
12 |
13 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
14 | val position = parent.getChildAdapterPosition(view)
15 | val itemCount = parent.adapter?.itemCount.orDef()
16 |
17 | val isLastPosition = position == itemCount - 1
18 |
19 | val itemHeight: Int = parent.height - 97.dp
20 |
21 | val lp = view.layoutParams as RecyclerView.LayoutParams
22 | lp.height = itemHeight
23 |
24 | var bottomMargin = 15.dp
25 | if (isLastPosition) {
26 | bottomMargin = 97.dp
27 | }
28 | lp.setMargins(15.dp, lp.topMargin, 15.dp, bottomMargin)
29 | view.layoutParams = lp
30 | super.getItemOffsets(outRect, view, parent, state)
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/weight/MomentAudioView.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.weight
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.LayoutInflater
6 | import android.view.View.OnClickListener
7 | import android.widget.ImageView
8 | import android.widget.TextView
9 | import androidx.lifecycle.Lifecycle
10 | import androidx.lifecycle.LifecycleObserver
11 | import androidx.lifecycle.OnLifecycleEvent
12 | import com.gcssloop.widget.RCRelativeLayout
13 | import com.lzx.musiclib.LifecycleUtils
14 | import com.lzx.musiclib.R
15 | import com.lzx.starrysky.SongInfo
16 | import com.lzx.starrysky.StarrySky
17 |
18 | class MomentAudioView : RCRelativeLayout, LifecycleObserver {
19 |
20 | private lateinit var ivPlay: ImageView
21 | private lateinit var title: TextView
22 | private lateinit var desc: TextView
23 |
24 | // private var isPlaying: Boolean = false
25 | private var songInfo: SongInfo? = null
26 | private var dynamicId: String = ""
27 |
28 | constructor(context: Context, ivPlay: ImageView) : super(context) {
29 | initView(context)
30 | }
31 |
32 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
33 | initView(context)
34 | }
35 |
36 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context,
37 | attrs, defStyleAttr) {
38 | initView(context)
39 | }
40 |
41 | private fun initView(context: Context) {
42 | LifecycleUtils.addObserver(context, this)
43 | LayoutInflater.from(context).inflate(R.layout.layout_moment_audio_view, this, true)
44 | ivPlay = findViewById(R.id.img_play)
45 | title = findViewById(R.id.title)
46 | desc = findViewById(R.id.desc)
47 | }
48 |
49 | fun setVoiceInfo(info: SongInfo?) {
50 | this.songInfo = info
51 | this.dynamicId = info?.songId.orEmpty()
52 | if (!StarrySky.with().isCurrMusicIsPlaying(info?.songId)) {
53 | ivPlay.setImageResource(R.drawable.moment_audio_view_pause)
54 | } else {
55 | ivPlay.setImageResource(R.drawable.moment_audio_view_play)
56 | }
57 | title.text = songInfo?.songName
58 | desc.text = songInfo?.artist
59 | }
60 |
61 | fun setUIState(isPlaying: Boolean) {
62 | if (isPlaying) {
63 | ivPlay.setImageResource(R.drawable.moment_audio_view_pause)
64 | } else {
65 | ivPlay.setImageResource(R.drawable.moment_audio_view_play)
66 | }
67 | }
68 |
69 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
70 | fun onDestroy() {
71 | LifecycleUtils.removeObserver(context, this)
72 | StarrySky.with().stopMusic()
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/weight/OnTabSelectListener.java:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.weight;
2 |
3 | public interface OnTabSelectListener {
4 | void onTabSelect(int position);
5 |
6 | void onTabReselect(int position);
7 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/weight/SquareImageView.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.weight
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 |
6 | class SquareImageView
7 | @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
8 | androidx.appcompat.widget.AppCompatImageView(context, attrs, defStyleAttr) {
9 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
10 | super.onMeasure(widthMeasureSpec, widthMeasureSpec)
11 | }
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/lzx/musiclib/weight/SquareView.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.musiclib.weight
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import android.view.View
6 |
7 | class SquareView
8 | @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
9 | View(context, attrs, defStyleAttr) {
10 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
11 | super.onMeasure(widthMeasureSpec, widthMeasureSpec)
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/anim/popup_bottom_enter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/popup_bottom_exit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/afb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/afb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/b0c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/b0c.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/b6e.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/b6e.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ba1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ba1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/bt_playpage_button_clock_normal_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/bt_playpage_button_clock_normal_new.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/bt_playpage_loop_press.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/bt_playpage_loop_press.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/dy_bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/dy_bg.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/gdt_ic_express_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/gdt_ic_express_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/gdt_ic_express_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/gdt_ic_express_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/gdt_ic_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/gdt_ic_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/gdt_ic_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/gdt_ic_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_accompaniment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_accompaniment.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_arrow_drop_down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_arrow_drop_down.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_danqu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_danqu.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_live_red_playing1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_live_red_playing1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_live_red_playing2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_live_red_playing2.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_live_red_playing3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_live_red_playing3.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_live_red_playing4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_live_red_playing4.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_live_red_playing5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_live_red_playing5.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_next_song.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_next_song.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_pre_song.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_pre_song.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_shunji.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_shunji.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_shunxu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_shunxu.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_songlist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_songlist.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_speed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_speed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_user.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_variable_speed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_variable_speed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_volume.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/ic_volume.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/icon_bar_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/icon_bar_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/icon_dynamic_top_next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/icon_dynamic_top_next.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/icon_dynamic_top_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/icon_dynamic_top_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/icon_dynamic_top_stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/icon_dynamic_top_stop.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/icon_dynamic_voice_loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/icon_dynamic_voice_loading.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/moment_audio_view_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/moment_audio_view_pause.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/moment_audio_view_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/moment_audio_view_play.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/note_btn_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/note_btn_close.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_dark_favorite_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_dark_favorite_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_dark_lyrics_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_dark_lyrics_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_dark_next_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_dark_next_pressed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_dark_pause_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_dark_pause_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_dark_play_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_dark_play_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_dark_prev_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_dark_prev_pressed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_favorite_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_favorite_checked.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_light_favorite_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_light_favorite_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_light_lyrics_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_light_lyrics_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_light_next_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_light_next_pressed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_light_pause_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_light_pause_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_light_play_normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_light_play_normal.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_light_prev_pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_light_prev_pressed.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/notify_btn_lyrics_checked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/notify_btn_lyrics_checked.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/recordfinish.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/recordfinish.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/rerecord.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/drawable-xxhdpi/rerecord.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/anim_playing.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
10 |
13 |
16 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/anim_round_rotate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_button_36dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_equalizer_band_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_play_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bg_spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_singer_more.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notify_btn_dark_next_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notify_btn_dark_pause_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notify_btn_dark_play_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notify_btn_dark_prev_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notify_btn_light_next_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notify_btn_light_pause_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notify_btn_light_play_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/notify_btn_light_prev_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/progress_equalizer_band.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
-
7 |
8 |
9 |
10 |
11 |
12 |
13 | -
14 |
15 |
16 |
17 |
18 |
19 |
20 | -
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | -
31 |
32 |
33 |
-
34 |
35 |
36 |
37 |
38 |
39 |
40 | -
41 |
42 |
43 |
44 |
45 |
46 |
47 | -
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/seek_bar_pro.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 |
9 |
10 | -
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/seek_bar_thumb.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_bg_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/shape_round_black.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activiity_dynamic_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
17 |
18 |
19 |
29 |
30 |
41 |
42 |
51 |
52 |
67 |
68 |
79 |
80 |
89 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_card.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
28 |
29 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_dynamic.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
31 |
32 |
41 |
42 |
50 |
51 |
58 |
59 |
72 |
73 |
81 |
82 |
89 |
90 |
101 |
102 |
110 |
111 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_effect.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
15 |
16 |
24 |
25 |
31 |
32 |
33 |
43 |
44 |
52 |
53 |
54 |
62 |
63 |
69 |
70 |
76 |
77 |
81 |
82 |
83 |
84 |
90 |
91 |
97 |
98 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_user.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
16 |
17 |
25 |
26 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_song_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_dynamic.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
31 |
32 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
16 |
17 |
21 |
22 |
31 |
32 |
40 |
41 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_dialog_song_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
24 |
25 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_dynamic.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
25 |
26 |
35 |
36 |
45 |
46 |
55 |
56 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_equalizer_band.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
23 |
24 |
25 |
26 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_home_music.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
21 |
22 |
31 |
32 |
47 |
48 |
61 |
62 |
63 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_preset.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_user.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
24 |
25 |
30 |
31 |
41 |
42 |
52 |
53 |
54 |
55 |
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout_moment_audio_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
20 |
21 |
31 |
32 |
40 |
41 |
47 |
48 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/slide_layout_tab.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
24 |
25 |
34 |
35 |
49 |
50 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/view_notify_play.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
26 |
27 |
37 |
38 |
49 |
50 |
51 |
57 |
58 |
67 |
68 |
79 |
80 |
90 |
91 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v19/style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #FFFFFF
8 | #F5F5F5
9 | #E0E0E0
10 | #BDBDBD
11 | #7E57C2
12 | #757575
13 | #7E57C2
14 | #757575
15 | #BDBDBD
16 | #9E9E9E
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StarrySky
3 | 播放列表
4 | 音效选择
5 | 塞纳河畔
6 | \n左岸的咖啡
7 | \n我手一杯
8 | \n品尝你的美
9 | \n留下唇印的嘴
10 | \n花店玫瑰
11 | \n名字写错谁
12 | \n告白气球
13 | \n风吹到对街
14 | \n微笑在天上飞
15 | \n你说你有点难追
16 | \n想让我知难而退
17 | \n礼物不需挑最贵
18 | \n只要香榭的落叶
19 | \n营造浪漫的约会
20 | \n不害怕搞砸一切
21 | \n拥有你就拥有
22 | \n全世界
23 | \n亲爱的
24 | \n爱上你
25 | \n从那天起
26 | \n甜蜜的很轻易
27 | \n亲爱的
28 | \n别任性
29 | \n你的眼睛
30 | \n在说我愿意
31 | \n塞纳河畔
32 | \n左岸的咖啡
33 | \n我手一杯
34 | \n品尝你的美
35 | \n留下唇印的嘴
36 | \n花店玫瑰
37 | \n名字写错谁
38 | \n告白气球
39 | \n风吹到对街
40 | \n微笑在天上飞
41 | \n你说你有点难追
42 | \n想让我知难而退
43 | \n礼物不需挑最贵
44 | \n只要香榭的落叶
45 | \n营造浪漫的约会
46 | \n不害怕搞砸一切
47 | \n拥有你就拥有
48 | \n全世界
49 | \n亲爱的
50 | \n爱上你
51 | \n从那天起
52 | \n甜蜜的很轻易
53 | \n亲爱的
54 | \n别任性
55 | \n你的眼睛
56 | \n在说我愿意
57 | \n亲爱的
58 | \n爱上你
59 | \n恋爱日记
60 | \n飘香水的回忆
61 | \n一整瓶
62 | \n的梦境
63 | \n全都有你
64 | \n搅拌在一起
65 | \n亲爱的
66 | \n别任性
67 | \n你的眼睛
68 | \n在说我愿意
69 |
70 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
18 |
19 |
23 |
24 |
42 |
43 |
--------------------------------------------------------------------------------
/art/StarrySky流程图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/StarrySky流程图.png
--------------------------------------------------------------------------------
/art/WechatIMG1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/WechatIMG1.jpeg
--------------------------------------------------------------------------------
/art/a4074094959_10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/a4074094959_10.jpg
--------------------------------------------------------------------------------
/art/biaoqing.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/biaoqing.gif
--------------------------------------------------------------------------------
/art/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/logo.jpg
--------------------------------------------------------------------------------
/art/notification1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/notification1.png
--------------------------------------------------------------------------------
/art/notification2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/notification2.png
--------------------------------------------------------------------------------
/art/notification3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/notification3.png
--------------------------------------------------------------------------------
/art/notification4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/notification4.png
--------------------------------------------------------------------------------
/art/qq_qun.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/qq_qun.jpg
--------------------------------------------------------------------------------
/art/成功案例.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/成功案例.png
--------------------------------------------------------------------------------
/art/成功案例副本.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/art/成功案例副本.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | ext {
6 | kotlin_version = '1.4.10'
7 | }
8 | repositories {
9 | jcenter()
10 | google()
11 | // maven {
12 | // url 'https://maven.google.com/'
13 | // name 'Google'
14 | // }
15 |
16 | }
17 | dependencies {
18 | classpath 'com.android.tools.build:gradle:4.0.1'
19 | // classpath 'com.novoda:bintray-release:0.9.1'
20 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
21 | }
22 | }
23 |
24 | allprojects {
25 | repositories {
26 | google()
27 | jcenter()
28 | maven { url "https://jitpack.io" }
29 | // maven { url "https://dl.bintray.com/lizixian/StarrySky/" }
30 | // maven {
31 | // url 'https://maven.google.com/'
32 | // name 'Google'
33 | // }
34 | maven { url "http://lib.gcssloop.com:8081/repository/gcssloop-central/" }
35 | }
36 | tasks.withType(JavaCompile) { //设置全局编码
37 | options.encoding = "UTF-8"
38 | }
39 | tasks.withType(Javadoc) { //设置文档编码
40 | options {
41 | encoding "UTF-8"
42 | charSet 'UTF-8'
43 | links "http://docs.oracle.com/javase/7/docs/api"
44 | }
45 | }
46 | task javadoc(type: Javadoc) {
47 | failOnError false
48 | }
49 | }
50 |
51 |
52 |
53 | task clean(type: Delete) {
54 | delete rootProject.buildDir
55 | }
56 |
57 |
58 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/Service相关配置.md:
--------------------------------------------------------------------------------
1 | ### Service相关配置
2 |
3 |
4 |
5 | 主要有这 4 个方法,可以让你在后台服务这方面有更多选择:
6 |
7 | **connService**: 是否需要后台服务,默认true,区别是播放器是不是运行在 Service 中,false的话所有逻辑是不经过 Service 的。
8 |
9 | **isStartService**:是否需要 startService,默认false,false的话就是只有 bindService
10 |
11 | **onlyStartService**:是否只是 startService 而不需要 startForegroundService,默认true
12 |
13 | **connServiceListener**:连接服务回调,可通过这个监听查看 Service 是否连接成功
14 |
15 | **startForegroundByWorkManager**:开关,可选择是否使用WorkManager来启动安卓12的后台服务,默认关闭
--------------------------------------------------------------------------------
/docs/SoundPool.md:
--------------------------------------------------------------------------------
1 | ### SoundPool
2 |
3 | StarrySky 提供 SoundPool 功能,适合播放短且频繁的音频。具体实现类是 **SoundPoolPlayback**
4 |
5 | 要使用 SoundPool 功能,需要通过 soundPool() API 去获取相关实例。下面是相关API:
6 |
7 | | 编号 | API | 作用 |
8 | |:----|:-----------------|:-------------------------------------------|
9 | | 1 | prepareForAssets | 从 assets 加载,参数 list 里面传文件名即可,不需要传整个路径 |
10 | | 2 | prepareForRaw | 从 raw 加载,参数 list 里面传 R.raw.xxx |
11 | | 3 | prepareForFile | 从 File 加载 |
12 | | 4 | prepareForPath | 从 本地路径 加载,参数 list 里面传完整路径 |
13 | | 5 | prepareForHttp | 从网络加载,参数 list 里面传 url地址 |
14 | | 6 | playSound | 播放,播放前请先完成加载音频,其他参数解释查看注释,方法返回 streamID |
15 | | 7 | pause | 暂停指定播放流的音效,参数 streamID 通过 playSound 返回 |
16 | | 8 | resume | 继续播放指定播放流的音效,参数 streamID 通过 playSound 返回 |
17 | | 9 | stop | 终止指定播放流的音效,参数 streamID 通过 playSound 返回 |
18 | | 10 | setLoop | 设置指定播放流的循环. |
19 | | 11 | setVolume | 设置指定播放流的音量. |
20 | | 12 | getVolume | 获取指定播放流当前音量 |
21 | | 13 | setPriority | 设置指定播放流的优先级,playSound 注释中已说明 priority 的作用. |
22 | | 14 | unload | 卸载一个指定的音频资源.注意参数是 soundID |
23 | | 15 | release | 释放SoundPool中的所有音频资源. |
24 |
25 | prepareForXXX 相关方法加载音频,只会加载一次,所以重复调用也没关系,如果要重新加载,请调用 release 方法
26 |
27 | 使用例子:
28 |
29 |
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | - 开始
2 |
3 | - [概述和初始化](介绍.md)
4 | - [开启缓存](缓存.md)
5 | - [通知栏](通知栏.md)
6 | - [全局拦截器](全局拦截器.md)
7 | - [Service相关配置](Service相关配置.md)
8 | - [其他配置](其他配置.md)
9 |
10 | - 播放相关
11 |
12 | - [相关api介绍](播放相关api介绍.md)
13 |
14 | - 多实例播放器
15 |
16 | - [多实例播放器](多实例播放器.md)
17 |
18 | - SoundPool
19 |
20 | - [SoundPool](SoundPool.md)
21 |
22 | - 音效相关
23 |
24 | - [音效相关](音效相关.md)
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/docs/介绍.md:
--------------------------------------------------------------------------------
1 | StarrySky 是一个音频集成库,鉴于在一些项目中如果需要集成音频播放功能的话,都离不开对播放器的封装,对播放控制的封装,对 API 的 封装等操作,其实这些操作在不同项目中都是大同小异的,所以
2 | StarrySky 就是这样一个集成了播放音频所需的操作的一个库。
3 |
4 | StarrySky 主打集成简单,代码小巧简单易读懂,扩展性强,使用方便等方向。经本人和众多开发者在实际项目中使用,证明了 灵活运用 StarrySky
5 | 是可以能够应付项目中足够复杂的播放场景的,欢迎尝试使用。
6 |
7 | ## 初始化
8 |
9 | 使用 **StarrySkyInstall** 类进行初始化操作,在 Application 中进行
10 |
11 |
12 |
13 |
14 | 通过 init 和 apply() 方法即可完成初始化。 当然也是说一定要在 Application 中进行,在你使用前初始化完成就可以。
--------------------------------------------------------------------------------
/docs/全局拦截器.md:
--------------------------------------------------------------------------------
1 | ### 添加全局拦截器
2 |
3 | 拦截器的功能是在播放前处理一些自己的操作,比如
4 | 播放器请求播放音频的 url,播放器请求一下某些权限等等操作。都可以通过拦截器去处理。
5 |
6 | 拦截器配置通过 **addInterceptor** 方法添加,可以添加多个拦截器,执行顺序跟添加顺序一致。
7 |
8 |
9 |
10 | addInterceptor 方法可填 2 个参数,第一个就是具体拦截器,第二个是执行线程,
11 | 传入 **InterceptorThread.IO** 该拦截器就会在子线程中执行,传入 **InterceptorThread.UI**
12 | 该拦截器就会在主线程中执行。默认是 InterceptorThread.UI
13 |
14 | 实现拦截器需要实现 **StarrySkyInterceptor** 类:
15 |
16 |
17 |
18 | **process** 方法里面是你的具体逻辑,他会在你播放音频前执行。参数 **songInfo** 是播放信息。**callback**
19 | 有两个方法 :
20 |
21 |
22 |
23 | 往下走要调用 onNext 才行,而 onInterrupt 即会中断逻辑并回调播放失败回调。
24 |
25 | 拦截器中 **getTag** 方法相当于是拦截器的名字,不同拦截器要取不一样的名字。
--------------------------------------------------------------------------------
/docs/其他配置.md:
--------------------------------------------------------------------------------
1 | ### 其他配置
2 |
3 | #### 自定义播放器
4 |
5 | StarrySky 默认使用的是 ExoPlayer,但如果你不想用,可以通过 **setPlayback** 方法去配置自己实现的播放器。
6 |
7 | 自定义播放器只需要实现 **Playback** 接口即可 , Playback 接口具体要实现什么方法可在代码中查看,里面都有注释。
8 | 当然某个方法实不实是随你的,不实现的话就没有对应的功能而已。
9 | 默认播放器实现类是 **ExoPlayback** ,自定义过程中可以作为参考。
10 |
11 | #### 设置全局状态监听器
12 | 通过 **setGlobalPlaybackStageListener** 方法即可设置全局状态监听器。无论你在哪个页面播放,这都会有回调。
13 |
14 | #### 是否自动焦点管理
15 | 音频播放都有焦点问题,StarrySky默认是把焦点管理交给 ExoPlayer,如果不想交给 ExoPlayer,可以通过配置
16 | **setAutoManagerFocus** 设置成 false 即可。
17 | false 的话,焦点管理是交给 **FocusManager** 这个类去管理的,并且可以获取到焦点变化状态,这个后面会介绍。
--------------------------------------------------------------------------------
/docs/多实例播放器.md:
--------------------------------------------------------------------------------
1 | ### 多实例播放器
2 |
3 | #### StarrySkyPlayer
4 |
5 | 由于 StarrySky 是单例模式,所以播放器对象只有一个,如果想多个对象,那么可以使用 StarrySkyPlayer
6 |
7 | StarrySkyPlayer 和 StarrySky 的区别就是 StarrySkyPlayer 不是单例。
8 |
9 | 在 StarrySkyPlayer 中,如果你不想使用初始化时的那些配置,那么你可以在构造函数中传入false即可,
10 | 然后通过对象相关 API 去重新配置:
11 |
12 |
13 |
14 | 在使用上,StarrySkyPlayer 的使用方式和 StarrySky 是一摸一样的,StarrySky 具有的功能它都有。
--------------------------------------------------------------------------------
/docs/缓存.md:
--------------------------------------------------------------------------------
1 | ## 开启缓存
2 |
3 | 在初始化中,还可以设置很多配置,通过 **setOpenCache(true)** 方法即可打开缓存功能。
4 |
5 |
6 |
7 | 缓存开关会存储在 Sp 中,通过 **StarrySkyConstant.KEY_CACHE_SWITCH** 即可获取开关状态。
8 |
9 | StarrySky 缓存分两种,一个是默认实现,一个是自定义实现。默认实现使用的是 ExoPlayer 自带的缓存功能,实现类是 **ExoCache** 默认使用默认实现。
10 |
11 | ### 如何自定义实现缓存功能
12 |
13 | 要自定义实现缓存功能,需要实现 **ICache** 接口,接口方法均有注释,这就不列出来了。这里举例以 **com.danikula:videocache** 库来实现一个自定义缓存为例子:
14 |
15 |
16 |
17 | 详细代码在项目 **TestApplication** 中可以找到。
18 |
19 | 定义好缓存后,通过 **setCache** 方法配置即可。
20 |
21 |
22 |
23 | ### 其他设置
24 |
25 | 在使用默认缓存的情况下,还可以设置缓存的路径以及大小等配置
26 |
27 |
28 |
29 | 很多反馈说缓存用不了的,其实是因为访问不了 sdcard,注意使用沙盒路径即可。
--------------------------------------------------------------------------------
/docs/音效相关.md:
--------------------------------------------------------------------------------
1 | ### 音效相关
2 |
3 | 音效相关的实现类是 **VoiceEffect** ,
4 | 使用的是系统的 Equalizer(均衡器),BassBoost(低音增强),Virtualizer(环绕声)
5 |
6 | 通过 effect() API 获取。
7 |
8 | 具体方法可查看 VoiceEffect 类,里面都有注释,使用例子参考 EffectActivity
--------------------------------------------------------------------------------
/extension-flac2120/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/extension-flac2120/README.md:
--------------------------------------------------------------------------------
1 | ### ExoPlayer FLAC 扩展
2 |
3 | 该 module 是 ExoPlayer flac 扩展编译后的代码。
4 |
5 | 地址:[extensions-flac](https://github.com/google/ExoPlayer/blob/release-v2/extensions/flac/README.md)
6 |
7 | 目前用的版本号是 2.12.0
8 |
9 | 编译脚本:
10 |
11 | ```shell script
12 | EXOPLAYER_ROOT="/Users/xxx/Downloads/ExoPlayer"
13 | FLAC_EXT_PATH="${EXOPLAYER_ROOT}/extensions/flac/src/main"
14 | NDK_PATH=/Users/xxx/Documents/android-ndk-r20b #这里填你解压出来的地址
15 | cd "${FLAC_EXT_PATH}/jni" && \
16 | curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.2.tar.xz | tar xJ && \
17 | mv flac-1.3.2 flac
18 | cd "${FLAC_EXT_PATH}"/jni && \
19 | ${NDK_PATH}/ndk-build APP_ABI=all -j4
20 | ```
21 |
22 | EXOPLAYER_ROOT 是 ExoPlayer 源码的路径
23 | FLAC_EXT_PATH 是源码里面flac扩展的文件路径
24 | ndk 用的是 android-ndk-r20b
--------------------------------------------------------------------------------
/extension-flac2120/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | //apply plugin: 'com.novoda.bintray-release'
3 |
4 | android {
5 | compileSdkVersion 28
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 28
10 | versionCode 100
11 | versionName "1.0.0"
12 |
13 | consumerProguardFiles "consumer-rules.pro"
14 |
15 | sourceSets {
16 | main {
17 | jniLibs.srcDir 'libs'
18 | jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
19 | }
20 | }
21 |
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_8
24 | targetCompatibility JavaVersion.VERSION_1_8
25 | }
26 | }
27 |
28 | buildTypes {
29 | release {
30 | minifyEnabled false
31 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
32 | }
33 | }
34 |
35 | // publish {
36 | // repoName = 'StarrySky'
37 | // userOrg = 'lizixian'
38 | // groupId = 'com.lzx'
39 | // artifactId = 'StarrySkyFlacExt'
40 | // publishVersion = '1.0.0'
41 | // desc = 'A Powerful and Streamline MusicLibrary'
42 | // website = "https://github.com/lizixian18/StarrySky"
43 | // }
44 | }
45 |
46 | dependencies {
47 | implementation fileTree(dir: "libs", include: ["*.jar"])
48 | implementation 'androidx.appcompat:appcompat:1.2.0'
49 | implementation "com.google.android.exoplayer:exoplayer-core:2.12.0"
50 | compileOnly 'org.checkerframework:checker-qual:3.3.0'
51 | }
--------------------------------------------------------------------------------
/extension-flac2120/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/extension-flac2120/consumer-rules.pro
--------------------------------------------------------------------------------
/extension-flac2120/libs/arm64-v8a/libflacJNI.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/extension-flac2120/libs/arm64-v8a/libflacJNI.so
--------------------------------------------------------------------------------
/extension-flac2120/libs/armeabi-v7a/libflacJNI.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/extension-flac2120/libs/armeabi-v7a/libflacJNI.so
--------------------------------------------------------------------------------
/extension-flac2120/libs/x86/libflacJNI.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/extension-flac2120/libs/x86/libflacJNI.so
--------------------------------------------------------------------------------
/extension-flac2120/libs/x86_64/libflacJNI.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/extension-flac2120/libs/x86_64/libflacJNI.so
--------------------------------------------------------------------------------
/extension-flac2120/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
--------------------------------------------------------------------------------
/extension-flac2120/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 | /
5 |
--------------------------------------------------------------------------------
/extension-flac2120/src/main/java/com/lzx/extension_flac2120/FlacDecoderException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.lzx.extension_flac2120;
17 |
18 | import com.google.android.exoplayer2.decoder.DecoderException;
19 |
20 | /** Thrown when an Flac decoder error occurs. */
21 | public final class FlacDecoderException extends DecoderException {
22 |
23 | /* package */ FlacDecoderException(String message) {
24 | super(message);
25 | }
26 |
27 | /* package */ FlacDecoderException(String message, Throwable cause) {
28 | super(message, cause);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/extension-flac2120/src/main/java/com/lzx/extension_flac2120/FlacLibrary.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.lzx.extension_flac2120;
17 |
18 | import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
19 | import com.google.android.exoplayer2.util.LibraryLoader;
20 |
21 | /**
22 | * Configures and queries the underlying native library.
23 | */
24 | public final class FlacLibrary {
25 |
26 | static {
27 | ExoPlayerLibraryInfo.registerModule("goog.exo.flac");
28 | }
29 |
30 | private static final LibraryLoader LOADER = new LibraryLoader("libflacJNI");
31 |
32 | private FlacLibrary() {}
33 |
34 | /**
35 | * Override the names of the Flac native libraries. If an application wishes to call this method,
36 | * it must do so before calling any other method defined by this class, and before instantiating
37 | * any {@link LibflacAudioRenderer} and {@link FlacExtractor} instances.
38 | *
39 | * @param libraries The names of the Flac native libraries.
40 | */
41 | public static void setLibraries(String... libraries) {
42 | LOADER.setLibraries(libraries);
43 | }
44 |
45 | /**
46 | * Returns whether the underlying library is available, loading it if necessary.
47 | */
48 | public static boolean isAvailable() {
49 | return LOADER.isAvailable();
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.enableJetifier=true
10 | android.useAndroidX=true
11 | org.gradle.jvmargs=-Xmx1536m
12 | # When configured, Gradle will run in incubating parallel mode.
13 | # This option should only be used with decoupled projects. More details, visit
14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
15 | # org.gradle.parallel=true
16 |
17 | # AndroidX package structure to make it clearer which packages are bundled with the
18 | # Android operating system, and which are packaged with your app's APK
19 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
20 | android.useAndroidX=true
21 | # Automatically convert third-party libraries to use AndroidX
22 | android.enableJetifier=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | //include ':extension-flac2120'
2 | include ':starrysky'
3 | include ':app'
4 |
--------------------------------------------------------------------------------
/starrysky/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/starrysky/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 | //apply plugin: 'com.novoda.bintray-release'
5 |
6 | android {
7 | compileSdkVersion 31
8 |
9 | defaultConfig {
10 | minSdkVersion 16
11 | targetSdkVersion 31
12 | versionCode 263
13 | versionName "2.6.3"
14 |
15 | compileOptions {
16 | sourceCompatibility JavaVersion.VERSION_1_8
17 | targetCompatibility JavaVersion.VERSION_1_8
18 | }
19 | }
20 |
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | consumerProguardFiles 'proguard-rules.pro'
26 | }
27 | }
28 |
29 | lintOptions {
30 | abortOnError false
31 | }
32 |
33 | androidExtensions {
34 | experimental = true
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = "1.8"
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation fileTree(dir: 'libs', include: ['*.jar'])
44 | //noinspection GradleCompatible
45 | implementation 'androidx.appcompat:appcompat:1.2.0'
46 | implementation 'androidx.work:work-runtime-ktx:2.7.1'
47 | api "com.google.android.exoplayer:exoplayer-core:2.14.1"
48 | api "com.google.android.exoplayer:extension-mediasession:2.14.1"
49 | compileOnly ("com.google.android.exoplayer:exoplayer-dash:2.14.1")
50 | compileOnly ("com.google.android.exoplayer:exoplayer-hls:2.14.1")
51 | compileOnly ("com.google.android.exoplayer:exoplayer-smoothstreaming:2.14.1")
52 | compileOnly ("com.google.android.exoplayer:extension-rtmp:2.14.1")
53 | compileOnly ("com.google.android.exoplayer:exoplayer-rtsp:2.14.1")
54 | compileOnly ("com.github.bumptech.glide:glide:4.11.0")
55 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
56 | }
57 | repositories {
58 | mavenCentral()
59 | }
60 |
--------------------------------------------------------------------------------
/starrysky/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 |
24 | -keep class com.lzx.starrysky.SongInfo { *; }
--------------------------------------------------------------------------------
/starrysky/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/AppLifecycleCallback.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky
2 |
3 | import android.app.Activity
4 | import android.app.Application.ActivityLifecycleCallbacks
5 | import android.os.Bundle
6 |
7 |
8 | class AppLifecycleCallback : ActivityLifecycleCallbacks {
9 |
10 | var currActivity: Activity? = null
11 | var activityList = mutableListOf()
12 | var activityStack = mutableListOf()
13 |
14 | fun getStackTopActivity() = activityStack.getOrNull(activityStack.lastIndex)
15 |
16 | override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
17 | activityStack.add(activity)
18 | }
19 |
20 | override fun onActivityStarted(activity: Activity) {
21 | }
22 |
23 | override fun onActivityResumed(activity: Activity) {
24 | currActivity = activity
25 | }
26 |
27 | override fun onActivityPaused(activity: Activity) {
28 | currActivity = null
29 | }
30 |
31 | override fun onActivityStopped(activity: Activity) {
32 | }
33 |
34 | override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
35 | }
36 |
37 | override fun onActivityDestroyed(activity: Activity) {
38 | activityStack.remove(activity)
39 | StarrySky.with().removeProgressListener(activity.toString())
40 | StarrySky.with().resetVariable(activity)
41 | }
42 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/OnPlayerEventListener.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky
2 |
3 | import com.lzx.starrysky.manager.PlaybackStage
4 |
5 | /**
6 | * 播放监听
7 | */
8 | interface OnPlayerEventListener {
9 | fun onPlaybackStageChange(stage: PlaybackStage)
10 | }
11 |
12 | /**
13 | * 进度监听
14 | */
15 | interface OnPlayProgressListener {
16 | fun onPlayProgress(currPos: Long, duration: Long)
17 | }
18 |
19 | /**
20 | * 全局状态监听
21 | * 有时候需要在状态改变时统一做些什么操作,
22 | * 比如播放时停止推拉流,停止时恢复,避免写得到处都是,可以用这个监听
23 | */
24 | interface GlobalPlaybackStageListener {
25 | fun onPlaybackStageChange(stage: PlaybackStage)
26 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/SongInfo.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky
2 |
3 | import android.graphics.Bitmap
4 | import android.os.Parcelable
5 | import com.lzx.starrysky.utils.md5
6 | import kotlinx.android.parcel.IgnoredOnParcel
7 | import kotlinx.android.parcel.Parcelize
8 |
9 | /**
10 | * 面向用户的音频信息
11 | */
12 | @Parcelize
13 | class SongInfo(
14 | var songId: String = "", //音乐id
15 | var songUrl: String = "", //音乐播放地址
16 | var songName: String = "", //音乐标题
17 | var artist: String = "", //作者
18 | var songCover: String = "", //音乐封面
19 | var duration: Long = -1, //音乐长度
20 | var decode: Boolean = false, //是否需要解码,如果要解码,最好用本地音频
21 | var headData: HashMap? = hashMapOf() //header 信息
22 | ) : Parcelable, Cloneable {
23 |
24 | companion object {
25 | @JvmStatic
26 | fun create(url: String): SongInfo = SongInfo().apply {
27 | songId = url.md5()
28 | songUrl = url
29 | }
30 |
31 | @JvmStatic
32 | fun create(id: String, url: String): SongInfo = SongInfo().apply {
33 | songId = id
34 | songUrl = url
35 | }
36 | }
37 |
38 | @IgnoredOnParcel
39 | var objectValue: Any = Any()
40 |
41 | @IgnoredOnParcel
42 | var coverBitmap: Bitmap? = null //音乐封面
43 |
44 | var tag: Any? = null //为某些错误做准备的标签
45 |
46 | override fun clone(): Any {
47 | var obj: Any? = null
48 | //调用Object类的clone方法,返回一个Object实例
49 | try {
50 | obj = super.clone()
51 | } catch (e: CloneNotSupportedException) {
52 | e.printStackTrace()
53 | }
54 | return obj ?: SongInfo()
55 | }
56 |
57 | override fun equals(other: Any?): Boolean {
58 | if (this === other) return true
59 | if (javaClass != other?.javaClass) return false
60 |
61 | other as SongInfo
62 |
63 | if (songId != other.songId) return false
64 | if (songUrl != other.songUrl) return false
65 | if (songName != other.songName) return false
66 | if (artist != other.artist) return false
67 | if (songCover != other.songCover) return false
68 | if (duration != other.duration) return false
69 | if (decode != other.decode) return false
70 | if (headData != other.headData) return false
71 | if (objectValue != other.objectValue) return false
72 |
73 | return true
74 | }
75 |
76 | override fun hashCode(): Int {
77 | var result = songId.hashCode()
78 | result = 31 * result + songUrl.hashCode()
79 | result = 31 * result + songName.hashCode()
80 | result = 31 * result + artist.hashCode()
81 | result = 31 * result + songCover.hashCode()
82 | result = 31 * result + duration.hashCode()
83 | result = 31 * result + decode.hashCode()
84 | result = 31 * result + (headData?.hashCode() ?: 0)
85 | result = 31 * result + objectValue.hashCode()
86 | return result
87 | }
88 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/StarrySky.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky
2 |
3 | import android.util.Log
4 | import com.lzx.starrysky.cache.ICache
5 | import com.lzx.starrysky.control.PlayerControl
6 | import com.lzx.starrysky.intercept.StarrySkyInterceptor
7 | import com.lzx.starrysky.notification.INotification
8 | import com.lzx.starrysky.utils.StarrySkyConstant
9 |
10 | // StarrySky -> PlayerControl -> PlaybackManager -> player
11 | object StarrySky {
12 |
13 | //播放控制
14 | private var playerControl: PlayerControl? = null
15 |
16 | /**
17 | * 获取全局拦截器集合
18 | */
19 | @JvmStatic
20 | fun interceptors(): MutableList> = StarrySkyInstall.interceptors
21 |
22 | /**
23 | * 清除全局拦截器
24 | */
25 | @JvmStatic
26 | fun clearInterceptor() {
27 | StarrySkyInstall.interceptors.clear()
28 | }
29 |
30 | internal fun log(msg: String) {
31 | if (StarrySkyInstall.isDebug) {
32 | Log.i("StarrySky", msg)
33 | }
34 | }
35 |
36 | fun getStackTopActivity() = StarrySkyInstall.appLifecycleCallback.getStackTopActivity()
37 |
38 | fun getActivityStack() = StarrySkyInstall.appLifecycleCallback.activityStack
39 |
40 | /**
41 | * 获取操作 api
42 | */
43 | @JvmStatic
44 | fun with(): PlayerControl {
45 | if (playerControl == null) {
46 | playerControl = PlayerControl(
47 | StarrySkyInstall.interceptors,
48 | StarrySkyInstall.globalPlaybackStageListener,
49 | getBinder()
50 | )
51 | }
52 | return playerControl!!
53 | }
54 |
55 | /**
56 | * 获取soundPool
57 | */
58 | @JvmStatic
59 | fun soundPool() = getBinder()?.soundPool
60 |
61 | /**
62 | * 切换系统和自定义通知栏
63 | */
64 | @JvmStatic
65 | fun changeNotification(notificationType: Int) {
66 | getBinder()?.changeNotification(notificationType)
67 | }
68 |
69 | /**
70 | * 关闭通知栏
71 | */
72 | @JvmStatic
73 | fun closeNotification() {
74 | getBinder()?.stopNotification()
75 | }
76 |
77 | /**
78 | * 打开通知栏
79 | */
80 | @JvmStatic
81 | fun openNotification() {
82 | getBinder()?.openNotification()
83 | }
84 |
85 | /**
86 | * 是否打开通知栏(开关性质的api)
87 | */
88 | @JvmStatic
89 | fun setIsOpenNotification(open: Boolean) {
90 | getBinder()?.setIsOpenNotification(open)
91 | }
92 |
93 | /**
94 | * 获取当前通知栏类型
95 | */
96 | @JvmStatic
97 | fun getNotificationType() = getBinder()?.getNotificationType()
98 | ?: INotification.SYSTEM_NOTIFICATION
99 |
100 | /**
101 | * 是否打开了缓存开关
102 | */
103 | @JvmStatic
104 | fun isOpenCache() = StarrySkyConstant.KEY_CACHE_SWITCH
105 |
106 | /**
107 | * 获取播放缓存类
108 | */
109 | @JvmStatic
110 | fun getPlayerCache(): ICache? = getBinder()?.getPlayerCache()
111 |
112 | /**
113 | * 音效相关,获取音效操作类
114 | */
115 | @JvmStatic
116 | fun effect() = StarrySkyInstall.voiceEffect
117 |
118 | /**
119 | * 音效相关,音效开关
120 | */
121 | fun effectSwitch(isOpen: Boolean) {
122 | StarrySkyConstant.keyEffectSwitch = isOpen
123 | if (isOpen) {
124 | effect().attachAudioEffect(with().getAudioSessionId())
125 | }
126 | }
127 |
128 | /**
129 | * 获取音效开关
130 | */
131 | fun getEffectSwitch() = StarrySkyConstant.keyEffectSwitch
132 |
133 | /**
134 | * 音效相关,音效配置信息是否要本地存储
135 | */
136 | fun saveEffectConfig(save: Boolean) {
137 | StarrySkyConstant.keySaveEffectConfig = save
138 | }
139 |
140 | /**
141 | * 获取图片加载器
142 | */
143 | internal fun getImageLoader() = StarrySkyInstall.imageLoader
144 |
145 | private fun getBinder() = StarrySkyInstall.binder
146 |
147 | /**
148 | * 对象类的全置空
149 | */
150 | @JvmStatic
151 | fun release() {
152 | StarrySkyInstall.release()
153 | playerControl?.release()
154 | playerControl = null
155 | }
156 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/cache/ExoCache.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.cache
2 |
3 | import android.content.Context
4 | import com.google.android.exoplayer2.C
5 | import com.google.android.exoplayer2.database.ExoDatabaseProvider
6 | import com.google.android.exoplayer2.upstream.cache.Cache
7 | import com.google.android.exoplayer2.upstream.cache.CacheSpan
8 | import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor
9 | import com.google.android.exoplayer2.upstream.cache.SimpleCache
10 | import com.lzx.starrysky.SongInfo
11 | import com.lzx.starrysky.utils.StarrySkyConstant
12 | import java.io.File
13 |
14 | class ExoCache(private val context: Context,
15 | private val cacheDir: String?,
16 | private val cacheMaxBytes: Long
17 | ) : ICache {
18 |
19 | private var cacheFile: File? = null
20 | private var exoCache: Cache? = null
21 |
22 | override fun getProxyUrl(url: String, songInfo: SongInfo): String? {
23 | //do nothing
24 | return null
25 | }
26 |
27 | override fun isOpenCache(): Boolean {
28 | return StarrySkyConstant.KEY_CACHE_SWITCH
29 | }
30 |
31 | override fun getCacheDirectory(context: Context, destFileDir: String?): File? {
32 | if (cacheFile == null && !destFileDir.isNullOrEmpty()) {
33 | cacheFile = File(destFileDir).apply { this.takeIf { cacheFile?.exists() == false }?.mkdirs() }
34 | }
35 | if (cacheFile == null) {
36 | cacheFile = context.getExternalFilesDir(null).takeIf { it == null } ?: context.filesDir
37 | }
38 | return cacheFile
39 | }
40 |
41 | /**
42 | * 获取缓存实例
43 | */
44 | @Synchronized
45 | fun getDownloadCache(): Cache? {
46 | if (exoCache == null) {
47 | val cacheFile = getCacheDirectory(context, cacheDir) ?: return null
48 | val cacheEvictor = LeastRecentlyUsedCacheEvictor(cacheMaxBytes)
49 | exoCache = SimpleCache(cacheFile, cacheEvictor, ExoDatabaseProvider(context))
50 | }
51 | return exoCache
52 | }
53 |
54 | override fun isCache(url: String): Boolean {
55 | val isCache: Boolean
56 | val cache = getDownloadCache()
57 | if (url.isNotEmpty()) {
58 | val cachedSpans = cache?.getCachedSpans(url)
59 | if (cachedSpans?.size == 0) {
60 | isCache = false
61 | } else {
62 | isCache = cache?.let {
63 | val contentLength = cache.getContentMetadata(url)["exo_len", C.LENGTH_UNSET.toLong()]
64 | var currentLength: Long = 0
65 | for (cachedSpan in cachedSpans ?: hashSetOf()) {
66 | currentLength += cache.getCachedLength(url, cachedSpan.position, cachedSpan.length)
67 | }
68 | return currentLength >= contentLength
69 | } ?: false
70 | }
71 | } else {
72 | isCache = false
73 | }
74 | return isCache
75 | }
76 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/cache/ICache.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.cache
2 |
3 | import android.content.Context
4 | import com.lzx.starrysky.SongInfo
5 | import java.io.File
6 |
7 | interface ICache {
8 |
9 | /**
10 | * 代理url,如果已经有缓存了,你可以用它来返回缓存地址,如果为空则用正常的 url
11 | */
12 | fun getProxyUrl(url: String, songInfo: SongInfo): String?
13 |
14 | /**
15 | * 是否打开缓存
16 | */
17 | fun isOpenCache(): Boolean = false
18 |
19 | /**
20 | * 获取缓存文件夹
21 | * destFileDir 文件夹路径
22 | */
23 | fun getCacheDirectory(context: Context, destFileDir: String?): File?
24 |
25 | /**
26 | * 是否已经有缓存
27 | */
28 | fun isCache(url: String): Boolean
29 | }
30 |
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/control/RepeatMode.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.control
2 |
3 | import androidx.annotation.IntDef
4 | import com.lzx.starrysky.utils.StarrySkyConstant
5 | import org.json.JSONObject
6 |
7 | data class RepeatMode(val repeatMode: Int, val isLoop: Boolean) {
8 | companion object {
9 | const val REPEAT_MODE_NONE = 100 //顺序播放
10 | const val REPEAT_MODE_ONE = 200 //单曲播放
11 | const val REPEAT_MODE_SHUFFLE = 300 //随机播放
12 | const val REPEAT_MODE_REVERSE = 400 //倒序播放
13 |
14 | fun saveRepeatMode(repeatMode: Int, isLoop: Boolean) {
15 | try {
16 | val jsonObject = JSONObject()
17 | jsonObject.put("repeatMode", repeatMode)
18 | jsonObject.put("isLoop", isLoop)
19 | StarrySkyConstant.KEY_REPEAT_MODE = jsonObject.toString()
20 | } catch (ex: Exception) {
21 | ex.printStackTrace()
22 | }
23 | }
24 |
25 | val with: RepeatMode
26 | get() {
27 | val json = StarrySkyConstant.KEY_REPEAT_MODE
28 | val defaultMode = RepeatMode(REPEAT_MODE_NONE, true)
29 | return if (json.isNullOrEmpty()) {
30 | defaultMode
31 | } else {
32 | try {
33 | val jsonObject = JSONObject(json)
34 | RepeatMode(jsonObject.getInt("repeatMode"), jsonObject.getBoolean("isLoop"))
35 | } catch (ex: Exception) {
36 | ex.printStackTrace()
37 | defaultMode
38 | }
39 | }
40 | }
41 | }
42 | }
43 |
44 | @IntDef(
45 | RepeatMode.REPEAT_MODE_NONE,
46 | RepeatMode.REPEAT_MODE_ONE,
47 | RepeatMode.REPEAT_MODE_SHUFFLE,
48 | RepeatMode.REPEAT_MODE_REVERSE
49 | )
50 | @Retention(AnnotationRetention.SOURCE)
51 | annotation class RepeatModeFlag
52 |
53 | fun Int.isModeNone() = this == RepeatMode.REPEAT_MODE_NONE
54 | fun Int.isModeOne() = this == RepeatMode.REPEAT_MODE_ONE
55 | fun Int.isModeShuffle() = this == RepeatMode.REPEAT_MODE_SHUFFLE
56 | fun Int.isModeReverse() = this == RepeatMode.REPEAT_MODE_REVERSE
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/intercept/InterceptorService.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.intercept
2 |
3 | import android.os.AsyncTask
4 | import com.lzx.starrysky.SongInfo
5 | import com.lzx.starrysky.utils.MainLooper
6 |
7 | class InterceptorService {
8 |
9 | private var interceptors = mutableListOf>()
10 |
11 | fun attachInterceptors(interceptors: MutableList>) {
12 | this.interceptors.clear()
13 | this.interceptors.addAll(interceptors)
14 | }
15 |
16 | fun handlerInterceptor(songInfo: SongInfo?, callback: InterceptCallback?) {
17 | if (interceptors.isNullOrEmpty()) {
18 | callback?.onNext(songInfo)
19 | } else {
20 | runCatching {
21 | doInterceptor(0, songInfo, callback)
22 | }.onFailure {
23 | callback?.onInterrupt(it.message)
24 | }
25 | }
26 | }
27 |
28 | private fun doInterceptor(index: Int, songInfo: SongInfo?, callback: InterceptCallback?) {
29 | if (index < interceptors.size) {
30 | val pair = interceptors[index]
31 | val interceptor = pair.first
32 | val interceptThread = pair.second
33 | if (interceptThread == InterceptorThread.UI) {
34 | MainLooper.instance.runOnUiThread {
35 | doInterceptImpl(interceptor, index, songInfo, callback)
36 | }
37 | } else {
38 | AsyncTask.THREAD_POOL_EXECUTOR.execute {
39 | doInterceptImpl(interceptor, index, songInfo, callback)
40 | }
41 | }
42 | } else {
43 | MainLooper.instance.runOnUiThread {
44 | callback?.onNext(songInfo)
45 | }
46 | }
47 | }
48 |
49 | private fun doInterceptImpl(
50 | interceptor: StarrySkyInterceptor,
51 | index: Int,
52 | songInfo: SongInfo?,
53 | callback: InterceptCallback?
54 | ) {
55 | interceptor.process(songInfo, object : InterceptCallback {
56 | override fun onNext(songInfo: SongInfo?) {
57 | doInterceptor(index + 1, songInfo, callback)
58 | }
59 |
60 | override fun onInterrupt(msg: String?) {
61 | MainLooper.instance.runOnUiThread {
62 | callback?.onInterrupt(msg)
63 | }
64 | }
65 | })
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/intercept/StarrySkyInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.intercept
2 |
3 | import com.lzx.starrysky.SongInfo
4 |
5 | object InterceptorThread {
6 | const val UI = "UI"
7 | const val IO = "IO"
8 | }
9 |
10 | abstract class StarrySkyInterceptor {
11 | abstract fun getTag(): String
12 | open fun process(songInfo: SongInfo?, callback: InterceptCallback) {}
13 | }
14 |
15 | interface InterceptCallback {
16 | /**
17 | * 执行下一个,用于上传一个文件
18 | */
19 | fun onNext(songInfo: SongInfo?)
20 |
21 | /**
22 | * 中断
23 | * msg:可以添加 msg
24 | */
25 | fun onInterrupt(msg: String?)
26 | }
27 |
28 |
29 |
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/manager/PlaybackStage.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.manager
2 |
3 | import com.lzx.starrysky.SongInfo
4 | import com.lzx.starrysky.playback.Playback
5 |
6 | class PlaybackStage {
7 | companion object {
8 | const val IDLE = "IDLE" //初始状态,播完完成,停止播放都会回调该状态
9 | const val PLAYING = "PLAYING" //开始播放,播放中
10 | const val SWITCH = "SWITCH" //切歌
11 | const val PAUSE = "PAUSE" //暂停
12 | const val BUFFERING = "BUFFERING" //缓冲
13 | const val ERROR = "ERROR" //出错
14 | }
15 |
16 | var lastSongInfo: SongInfo? = null //上一个音频信息(切歌回调时有用)
17 | var songInfo: SongInfo? = null //当前音频信息
18 | var isStop: Boolean = false //由于播完完成和停止播放都会回调IDEA,如果要做区分的话可以用这个字段,停止就是true,播放完成就是false
19 | var errorMsg: String? = null
20 | var stage: String = IDLE
21 | }
22 |
23 | fun Int.changePlaybackState(): String {
24 | return when (this) {
25 | Playback.STATE_IDLE -> PlaybackStage.IDLE
26 | Playback.STATE_BUFFERING -> PlaybackStage.BUFFERING
27 | Playback.STATE_PLAYING -> PlaybackStage.PLAYING
28 | Playback.STATE_PAUSED -> PlaybackStage.PAUSE
29 | Playback.STATE_ERROR -> PlaybackStage.ERROR
30 | else -> PlaybackStage.IDLE
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/notification/NotificationManager.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.notification
2 |
3 | import android.content.Context
4 |
5 | class NotificationManager {
6 |
7 | interface NotificationFactory {
8 | fun build(context: Context, config: NotificationConfig?): INotification
9 | }
10 |
11 | fun getSystemNotification(context: Context, config: NotificationConfig?) = SYSTEM_NOTIFICATION_FACTORY.build(context, config)
12 |
13 | fun getCustomNotification(context: Context, config: NotificationConfig?) = CUSTOM_NOTIFICATION_FACTORY.build(context, config)
14 |
15 | companion object {
16 | val SYSTEM_NOTIFICATION_FACTORY: NotificationFactory = object : NotificationFactory {
17 | override fun build(
18 | context: Context, config: NotificationConfig?
19 | ): INotification {
20 | return if (config == null) SystemNotification(context) else SystemNotification(context, config)
21 | }
22 | }
23 |
24 | val CUSTOM_NOTIFICATION_FACTORY: NotificationFactory = object : NotificationFactory {
25 | override fun build(
26 | context: Context, config: NotificationConfig?
27 | ): INotification {
28 | return if (config == null) CustomNotification(context) else CustomNotification(context, config)
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/notification/imageloader/ImageLoader.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.notification.imageloader
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.graphics.drawable.Drawable
6 |
7 | /**
8 | * 图片加载类
9 | * 策略或者静态代理模式,开发者只需要关心ImageLoader + ImageLoaderStrategy
10 | */
11 | open class ImageLoader(private val context: Context?) {
12 | private var mLoader: ImageLoaderStrategy? = null
13 |
14 | fun init(loader: ImageLoaderStrategy) {
15 | this.mLoader = loader
16 | }
17 |
18 | fun load(url: String, callBack: ImageLoaderCallBack) {
19 | if (mLoader == null) {
20 | mLoader = DefaultImageLoader()
21 | }
22 | context?.let { mLoader?.loadImage(it, url, callBack) }
23 | }
24 | }
25 |
26 | interface ImageLoaderStrategy {
27 | fun loadImage(context: Context, url: String?, callBack: ImageLoaderCallBack)
28 | }
29 |
30 | interface ImageLoaderCallBack {
31 | fun onBitmapLoaded(bitmap: Bitmap?)
32 |
33 | fun onBitmapFailed(errorDrawable: Drawable?)
34 | }
35 |
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/notification/utils/NotificationUtils.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.notification.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Notification
5 | import android.app.NotificationChannel
6 | import android.app.NotificationManager
7 | import android.app.PendingIntent
8 | import android.app.Service
9 | import android.content.Context
10 | import android.content.Intent
11 | import android.net.Uri
12 | import android.os.Build
13 | import android.os.Bundle
14 | import androidx.annotation.RequiresApi
15 | import androidx.core.app.NotificationCompat
16 | import com.lzx.starrysky.R
17 | import com.lzx.starrysky.SongInfo
18 | import com.lzx.starrysky.notification.INotification
19 | import com.lzx.starrysky.notification.INotification.Companion.CHANNEL_ID
20 | import com.lzx.starrysky.notification.NotificationConfig
21 |
22 | /**
23 | * 通知栏工具类,主要提供一些公共的方法
24 | */
25 | object NotificationUtils {
26 |
27 |
28 | /**
29 | * 设置content点击事件
30 | */
31 | fun createContentIntent(
32 | context: Context, config: NotificationConfig?,
33 | songInfo: SongInfo?, bundle: Bundle?, targetClass: Class<*>
34 | ): PendingIntent {
35 | //构建 Intent
36 | val openUI = Intent(context, targetClass)
37 | // openUI.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
38 | openUI.putExtra("notification_entry", INotification.ACTION_INTENT_CLICK)
39 | songInfo?.let {
40 | openUI.putExtra("songInfo", it)
41 | }
42 | bundle?.let {
43 | openUI.putExtra("bundleInfo", it)
44 | }
45 | //构建 PendingIntent
46 | @SuppressLint("WrongConstant")
47 | val pendingIntent: PendingIntent
48 | val requestCode = INotification.REQUEST_CODE
49 | val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
50 | PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
51 | } else {
52 | PendingIntent.FLAG_CANCEL_CURRENT
53 | }
54 | pendingIntent = when (config?.pendingIntentMode) {
55 | NotificationConfig.MODE_ACTIVITY -> {
56 | PendingIntent.getActivity(context, requestCode, openUI, flags)
57 | }
58 | NotificationConfig.MODE_BROADCAST -> {
59 | PendingIntent.getBroadcast(context, requestCode, openUI, flags)
60 | }
61 | NotificationConfig.MODE_SERVICE -> {
62 | PendingIntent.getService(context, requestCode, openUI, flags)
63 | }
64 | else -> PendingIntent.getActivity(context, requestCode, openUI, flags)
65 | }
66 | return pendingIntent
67 | }
68 |
69 | /**
70 | * 兼容8.0
71 | */
72 | @RequiresApi(Build.VERSION_CODES.O)
73 | fun createNotificationChannel(
74 | context: Context,
75 | manager: NotificationManager
76 | ) {
77 | if (manager.getNotificationChannel(CHANNEL_ID) == null) {
78 | val notificationChannel = NotificationChannel(
79 | CHANNEL_ID,
80 | context.getString(R.string.notification_channel),
81 | NotificationManager.IMPORTANCE_LOW
82 | )
83 | notificationChannel.description = context.getString(R.string.notification_channel_description)
84 | manager.createNotificationChannel(notificationChannel)
85 | }
86 | }
87 |
88 |
89 | fun createNoCrashNotification(context: Context): Notification {
90 | val notifyBuilder = if (Build.VERSION.SDK_INT >= 26) {
91 | val manager = context.getSystemService(Service.NOTIFICATION_SERVICE) as NotificationManager
92 | createNotificationChannel(context, manager)
93 | val builder = NotificationCompat.Builder(context, CHANNEL_ID)
94 | builder.setVibrate(longArrayOf(0L))
95 | builder.setSound(null as Uri?)
96 | builder.setDefaults(0)
97 | builder
98 | } else {
99 | NotificationCompat.Builder(context)
100 | }
101 | return notifyBuilder
102 | .setContentTitle("防止崩溃notification")
103 | .setSmallIcon(R.drawable.ic_notification).build()
104 |
105 | }
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/playback/Playback.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.playback
2 |
3 | import com.lzx.starrysky.SongInfo
4 |
5 |
6 | /**
7 | * 播放器接口,如果要实现其他播放器,实现该接口即可
8 | */
9 | interface Playback {
10 |
11 | companion object {
12 | const val STATE_IDLE = 1 //空闲(默认状态,播放完成,停止播放后 也会回调)
13 | const val STATE_BUFFERING = 2 //正在缓冲
14 | const val STATE_PLAYING = 3 //正在播放
15 | const val STATE_PAUSED = 4 //暂停
16 | const val STATE_ERROR = 6 //出错
17 | }
18 |
19 | /**
20 | * 获取当前播放状态(上面那几种)
21 | */
22 | fun playbackState(): Int
23 |
24 | /**
25 | * 是否在播放
26 | */
27 | fun isPlaying(): Boolean
28 |
29 | /**
30 | * 当前播放进度
31 | */
32 | fun currentStreamPosition(): Long
33 |
34 | /**
35 | * 当前缓冲进度
36 | */
37 | fun bufferedPosition(): Long
38 |
39 | /**
40 | * 时长
41 | */
42 | fun duration(): Long
43 |
44 | /**
45 | * 当前播放的songId
46 | */
47 | var currentMediaId: String
48 |
49 | /**
50 | * 当前音量
51 | */
52 | fun setVolume(volume: Float)
53 |
54 | fun getVolume(): Float
55 |
56 | /**
57 | * 当前播放的 songInfo
58 | */
59 | fun getCurrPlayInfo(): SongInfo?
60 |
61 | /**
62 | * 获取 AudioSessionId
63 | */
64 | fun getAudioSessionId(): Int
65 |
66 | /**
67 | * 停止
68 | */
69 | fun stop()
70 |
71 | /**
72 | * 播放
73 | * songInfo 要播放的音频
74 | * isPlayWhenReady 准备好之后是否要马上播放
75 | */
76 | fun play(songInfo: SongInfo, isPlayWhenReady: Boolean)
77 |
78 | /**
79 | * 暂停
80 | */
81 | fun pause()
82 |
83 | /**
84 | * 转跳进度
85 | */
86 | fun seekTo(position: Long)
87 |
88 | /**
89 | * 快进
90 | */
91 | fun onFastForward(speed: Float)
92 |
93 | /**
94 | * 快退
95 | */
96 | fun onRewind(speed: Float)
97 |
98 | /**
99 | * 指定语速 refer 是否已当前速度为基数 multiple 倍率
100 | */
101 | fun onDerailleur(refer: Boolean, multiple: Float)
102 |
103 | /**
104 | * 获取速度
105 | */
106 | fun getPlaybackSpeed(): Float
107 |
108 | /**
109 | * 下一首
110 | */
111 | fun skipToNext()
112 |
113 | /**
114 | * 上一首
115 | */
116 | fun skipToPrevious()
117 |
118 | interface Callback {
119 | /**
120 | * 其他播放状态回调
121 | */
122 | fun onPlayerStateChanged(songInfo: SongInfo?, playWhenReady: Boolean, playbackState: Int)
123 |
124 | /**
125 | * 播放出错回调
126 | */
127 | fun onPlaybackError(songInfo: SongInfo?, error: String)
128 |
129 | /**
130 | * 焦点改变
131 | */
132 | fun onFocusStateChange(info: FocusInfo)
133 |
134 | /**
135 | * 下一首
136 | */
137 | fun skipToNext()
138 |
139 | /**
140 | * 上一首
141 | */
142 | fun skipToPrevious()
143 | }
144 |
145 | /**
146 | * 设置回调
147 | */
148 | fun setCallback(callback: Callback?)
149 | }
150 |
151 | /**
152 | * songInfo : 当前播放的音频信息
153 | *
154 | * audioFocusState:焦点状态,4 个值:
155 | * STATE_NO_FOCUS -> 当前没有音频焦点
156 | * STATE_HAVE_FOCUS -> 所请求的音频焦点当前处于保持状态
157 | * STATE_LOSS_TRANSIENT -> 音频焦点已暂时丢失
158 | * STATE_LOSS_TRANSIENT_DUCK -> 音频焦点已暂时丢失,但播放时音量可能会降低
159 | *
160 | * playerCommand:播放指令,3 个值:
161 | * DO_NOT_PLAY -> 不要播放
162 | * WAIT_FOR_CALLBACK -> 等待回调播放
163 | * PLAY_WHEN_READY -> 可以播放
164 | *
165 | * volume:焦点变化后推荐设置的音量,两个值:
166 | * VOLUME_DUCK -> 0.2f
167 | * VOLUME_NORMAL -> 1.0f
168 | */
169 | data class FocusInfo(var songInfo: SongInfo?, var audioFocusState: Int, var playerCommand: Int, var volume: Float)
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/queue/MediaQueueManager.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.queue
2 |
3 | import android.graphics.Bitmap
4 | import android.graphics.drawable.Drawable
5 | import com.lzx.starrysky.SongInfo
6 | import com.lzx.starrysky.StarrySky
7 | import com.lzx.starrysky.control.RepeatMode
8 | import com.lzx.starrysky.control.isModeOne
9 | import com.lzx.starrysky.control.isModeShuffle
10 | import com.lzx.starrysky.notification.imageloader.ImageLoaderCallBack
11 | import com.lzx.starrysky.utils.isIndexPlayable
12 |
13 | class MediaQueueManager(val provider: MediaSourceProvider) {
14 | private var currentIndex: Int = 0
15 |
16 | /**
17 | * ignoreShuffle 是否忽略随机模式
18 | */
19 | fun getCurrentSongInfo(ignoreShuffle: Boolean): SongInfo? {
20 | val repeatMode = RepeatMode.with.repeatMode
21 | val playingQueue = if (!ignoreShuffle && repeatMode.isModeShuffle()) {
22 | provider.getShuffleSongList()
23 | } else {
24 | provider.songList
25 | }
26 | return playingQueue.elementAtOrNull(currentIndex)
27 | }
28 |
29 | fun getCurrSongList(): MutableList {
30 | val repeatMode = RepeatMode.with.repeatMode
31 | return if (repeatMode.isModeShuffle()) {
32 | provider.getShuffleSongList()
33 | } else {
34 | provider.songList
35 | }
36 | }
37 |
38 | fun skipQueuePosition(amount: Int): Boolean {
39 | val playingQueue = provider.songList
40 |
41 | if (playingQueue.size == 0) {
42 | return false
43 | }
44 | var index = currentIndex + amount
45 | if (index < 0) {
46 | val repeatMode = RepeatMode.with
47 | index = if (repeatMode.isLoop) {
48 | playingQueue.size - 1
49 | } else {
50 | if (repeatMode.repeatMode.isModeOne() || repeatMode.repeatMode.isModeShuffle()) {
51 | playingQueue.lastIndex
52 | } else {
53 | 0
54 | }
55 | }
56 | } else {
57 | index %= playingQueue.size
58 | }
59 | if (!index.isIndexPlayable(playingQueue)) {
60 | return false
61 | }
62 | currentIndex = index
63 | StarrySky.log("skipQueuePosition#mCurrentIndex=$currentIndex")
64 | return true
65 | }
66 |
67 | fun currSongIsFirstSong(): Boolean {
68 | val firstSong = provider.getSongInfoByIndex(0)
69 | return getCurrentSongInfo(true)?.songId == firstSong?.songId
70 | }
71 |
72 | fun currSongIsLastSong(): Boolean {
73 | val lastSong = provider.getSongInfoByIndex(provider.songList.lastIndex)
74 | return getCurrentSongInfo(true)?.songId == lastSong?.songId
75 | }
76 |
77 | fun updateIndexBySongId(songId: String): Boolean {
78 | val index = if (RepeatMode.with.repeatMode.isModeShuffle()) {
79 | provider.getIndexById(songId,true)
80 | } else {
81 | provider.getIndexById(songId)
82 | }
83 | val list = provider.songList
84 | if (index.isIndexPlayable(list)) {
85 | currentIndex = index
86 | }
87 | return index >= 0
88 | }
89 |
90 | fun updateIndexByPlayingInfo(currInfo: SongInfo?) {
91 | currInfo?.let {
92 | updateIndexBySongId(it.songId)
93 | }
94 | }
95 |
96 | fun updateMusicArt(songInfo: SongInfo?) {
97 | //更新封面 bitmap
98 | val coverUrl = songInfo?.songCover.orEmpty()
99 | if (coverUrl.isNotEmpty() && songInfo?.coverBitmap == null) {
100 | StarrySky.getImageLoader()?.load(coverUrl, object : ImageLoaderCallBack {
101 | override fun onBitmapLoaded(bitmap: Bitmap?) {
102 | songInfo?.let {
103 | it.coverBitmap = bitmap
104 | provider.updateMusicArt(songInfo)
105 | }
106 | }
107 |
108 | override fun onBitmapFailed(errorDrawable: Drawable?) {
109 | }
110 | })
111 | }
112 | }
113 |
114 | fun getCurrIndex() = currentIndex
115 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/queue/MediaSourceProvider.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.queue
2 |
3 | import com.lzx.starrysky.SongInfo
4 | import com.lzx.starrysky.utils.isIndexPlayable
5 |
6 | /**
7 | * 存储播放数据源
8 | */
9 | class MediaSourceProvider {
10 | //数据源
11 | private var songSources = linkedMapOf()
12 |
13 | //随机模式下的数据源
14 | private var shuffleSongSources = mutableListOf()
15 |
16 | var songList: MutableList
17 | get() {
18 | val list = mutableListOf()
19 | songSources.forEach {
20 | list.add(it.value)
21 | }
22 | return list
23 | }
24 | set(value) {
25 | songSources.clear()
26 | value.forEach {
27 | songSources[it.songId] = it
28 | }
29 | updateShuffleSongList()
30 | }
31 |
32 | fun updateShuffleSongList() {
33 | if (shuffleSongSources.isNotEmpty()) {
34 | shuffleSongSources.clear()
35 | }
36 | shuffleSongSources.addAll(songList)
37 | shuffleSongSources.shuffle()
38 | }
39 |
40 | fun getSourceSize() = songSources.size
41 |
42 | fun getShuffleSongList(): MutableList {
43 | if (shuffleSongSources.isEmpty()) {
44 | updateShuffleSongList()
45 | }
46 | return shuffleSongSources
47 | }
48 |
49 | fun addSongInfo(info: SongInfo) {
50 | if (!hasSongInfo(info.songId)) {
51 | songSources[info.songId] = info
52 | updateShuffleSongList()
53 | }
54 | }
55 |
56 | fun addSongInfo(index: Int, info: SongInfo) {
57 | if (!hasSongInfo(info.songId)) {
58 | val list = mutableListOf>()
59 | songSources.forEach {
60 | list.add(Pair(it.key, it.value))
61 | }
62 | if (index.isIndexPlayable(list)) {
63 | list.add(index, Pair(info.songId, info))
64 | }
65 | songSources.clear()
66 | list.forEach {
67 | songSources[it.first] = it.second
68 | }
69 | updateShuffleSongList()
70 | }
71 | }
72 |
73 | fun addSongInfos(infos: MutableList) {
74 | infos.forEach {
75 | addSongInfo(it)
76 | }
77 | }
78 |
79 | fun clearSongInfos() {
80 | songList.clear()
81 | songSources.clear()
82 | }
83 |
84 | fun deleteSongInfoById(songId: String): Boolean {
85 | if (hasSongInfo(songId)) {
86 | songSources.remove(songId)
87 | return true
88 | }
89 | return false
90 | }
91 |
92 | fun hasSongInfo(songId: String): Boolean {
93 | return songSources.containsKey(songId)
94 | }
95 |
96 | fun getSongInfoById(songId: String): SongInfo? {
97 | if (songId.isEmpty()) {
98 | return null
99 | }
100 | return songSources.getOrElse(songId) { null }
101 | }
102 |
103 | fun getSongInfoByIndex(index: Int): SongInfo? {
104 | return songList.elementAtOrNull(index)
105 | }
106 |
107 | fun getIndexById(songId: String, isShuffle:Boolean = false): Int {
108 | val info = getSongInfoById(songId)
109 | return if (info != null) {
110 | when (isShuffle) {
111 | true -> shuffleSongSources.indexOf(info)
112 | false -> songList.indexOf(info)
113 | }
114 | } else -1
115 | }
116 |
117 | fun updateMusicArt(songInfo: SongInfo) {
118 | songSources[songInfo.songId] = songInfo
119 | }
120 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/service/WifiLockHelper.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.service
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.net.ConnectivityManager
6 | import android.net.NetworkInfo
7 | import android.net.wifi.WifiInfo
8 | import android.net.wifi.WifiManager
9 | import android.os.Build
10 | import android.os.PowerManager
11 | import android.telephony.TelephonyManager
12 |
13 | /**
14 | * 唤醒锁与Wifi锁的辅助类
15 | */
16 | object WifiLockHelper {
17 | private var mWifiLock: WifiManager.WifiLock? = null
18 | private var mCellularLock: PowerManager.WakeLock? = null
19 |
20 | /**
21 | * Locks the network and start using it. Later, the network must be unlocked using @release.
22 | * 锁定网络并开始使用它。之后,必须使用@release()解锁网络。
23 | * 这个函数就做两个事情,获取激活的网络,判断网络如果是Wifi,就获取WifiLock,如果是移动网络就获取PowerManager.WakeLock
24 | * @return true if succeed, false otherwise.
25 | */
26 | @SuppressLint("InvalidWakeLockTag")
27 | fun acquire(context: Context) {
28 | if (isWifiConnected(context) && mWifiLock == null) {
29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
30 | context.getSystemService(WifiManager::class.java)?.let {
31 | mWifiLock = it.createWifiLock(WifiManager.WIFI_MODE_FULL, "LockHelper Lock_Wifi")
32 | it.connectionInfo?.let { wifiInfo ->
33 | val detailedState = WifiInfo.getDetailedStateOf(wifiInfo.supplicantState)
34 | if (detailedState == NetworkInfo.DetailedState.CONNECTED // 此状态表示IP通信应该可用
35 | || detailedState == NetworkInfo.DetailedState.CONNECTING // 此状态表示当前正在建立数据连接
36 | || detailedState == NetworkInfo.DetailedState.OBTAINING_IPADDR
37 | ) { // 此状态表示等待DHCP服务器的响应,以便分配IP地址信息。
38 | mWifiLock?.acquire() // 获取Wifi锁,这样可以保证用户熄屏空闲时Wifi不会断开。
39 | }
40 |
41 | }
42 | }
43 | }
44 | } else if (mCellularLock == null && isMobileNetwork(context)) {
45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
46 | context.getSystemService(PowerManager::class.java)?.let {
47 | mCellularLock = it.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WifiLockHelper Lock_Cellular")
48 | mCellularLock?.acquire(10 * 60 * 1000L /*10 minutes*/)
49 | }
50 | }
51 | }
52 | }
53 |
54 | /**
55 | * 判断wifi是否连接状态
56 | *
57 | * 需添加权限 android.permission.ACCESS_NETWORK_STATE
58 | *
59 | * @param context 上下文
60 | * @return true: 连接并可用
false: 未连接或者不可用
61 | */
62 | private fun isWifiConnected(context: Context): Boolean {
63 | val cm = context
64 | .getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
65 | return cm?.activeNetworkInfo?.type == ConnectivityManager.TYPE_WIFI
66 | && cm.activeNetworkInfo?.isAvailable == true
67 | }
68 |
69 | private fun isMobileNetwork(context: Context): Boolean {
70 | (context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager)?.activeNetworkInfo?.let {
71 | val netType = it.type
72 | /*
73 | ConnectivityManager.TYPE_WIMAX :即全球微波互联接入。是一项新兴的宽带无线接入技术,能提供面向互联网的高速连接,
74 | 数据传输距离最远可达50km。WiMAX还具有QoS保障、传输速率高、业务丰富多样等优点。WiMAX的技术起点较高,
75 | 采用了代表未来通信技术发展方向的OFDM/OFDMA、AAS、MIMO等先进技术,随着技术标准的发展,WiMAX逐步实现宽带业务的移动化,
76 | 而3G则实现移动业务的宽带化,两种网络的融合程度会越来越高。
77 | */
78 | if ((netType == ConnectivityManager.TYPE_MOBILE || netType == ConnectivityManager.TYPE_WIMAX)) {
79 | val netSubType = it.subtype
80 | // 下面的判断写得像放屁一样,总结就是netSubType >= 1的都认为是移动网络,这就包含了所有的子网络类型了。所以这里可以直接返回true的,不需要判断子网络类型
81 | return ((netSubType >= TelephonyManager.NETWORK_TYPE_UMTS) // 值是3
82 | || // HACK
83 | (netSubType == TelephonyManager.NETWORK_TYPE_GPRS) // 值是1
84 | || (netSubType == TelephonyManager.NETWORK_TYPE_EDGE)) // 值是2
85 | } else {
86 | return false
87 | }
88 |
89 | } ?: return false
90 | }
91 |
92 | /**
93 | * Unlocks the network and stop using it. The network must be locked first using @ref acquire.
94 | * @return true is succeed, false otherwise.
95 | */
96 | fun release() {
97 | if (mWifiLock?.isHeld == true) {
98 | mWifiLock?.release()
99 | }
100 | mWifiLock = null
101 | if (mCellularLock?.isHeld == true) {
102 | mCellularLock?.release()
103 | }
104 | mCellularLock = null
105 | }
106 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/utils/KtPreferences.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.utils
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.content.SharedPreferences
6 | import android.preference.PreferenceManager
7 | import kotlin.properties.ReadWriteProperty
8 | import kotlin.reflect.KProperty
9 |
10 | /**
11 | * 基于委托实现的sp简单封装
12 | */
13 | abstract class KtPreferences {
14 |
15 | companion object {
16 | var context: Context? = null
17 |
18 | @JvmStatic
19 | fun init(context: Context?) {
20 | if (Companion.context == null) {
21 | Companion.context = context
22 | }
23 | }
24 | }
25 |
26 | private val preferences: SharedPreferences by lazy {
27 | PreferenceManager.getDefaultSharedPreferences(context)
28 | }
29 |
30 | fun booleanPref(default: Boolean = false, synchronous: Boolean = false) = object : ReadWriteProperty {
31 | override fun setValue(thisRef: Any, property: KProperty<*>, value: Boolean) {
32 | preferences.edit().putBoolean(property.name, value).execute(synchronous)
33 | }
34 |
35 | override fun getValue(thisRef: Any, property: KProperty<*>): Boolean {
36 | return preferences.getBoolean(property.name, default)
37 | }
38 | }
39 |
40 | fun intPref(default: Int = 0, synchronous: Boolean = false) = object : ReadWriteProperty {
41 | override fun setValue(thisRef: Any, property: KProperty<*>, value: Int) {
42 | preferences.edit().putInt(property.name, value).execute(synchronous)
43 | }
44 |
45 | override fun getValue(thisRef: Any, property: KProperty<*>): Int {
46 | return preferences.getInt(property.name, default)
47 | }
48 | }
49 |
50 | fun stringPref(default: String = "", synchronous: Boolean = false) = object : ReadWriteProperty {
51 | override fun setValue(thisRef: Any, property: KProperty<*>, value: String?) {
52 | preferences.edit().putString(property.name, value).execute(synchronous)
53 | }
54 |
55 | override fun getValue(thisRef: Any, property: KProperty<*>): String? {
56 | return preferences.getString(property.name, default)
57 | }
58 | }
59 |
60 | fun longPref(default: Long = 0L, synchronous: Boolean = false) = object : ReadWriteProperty {
61 | override fun setValue(thisRef: Any, property: KProperty<*>, value: Long) {
62 | preferences.edit().putLong(property.name, value).execute(synchronous)
63 | }
64 |
65 | override fun getValue(thisRef: Any, property: KProperty<*>): Long {
66 | return preferences.getLong(property.name, default)
67 | }
68 | }
69 |
70 | fun floatPref(defaultValue: Float = 0.0f, synchronous: Boolean = false) = object : ReadWriteProperty {
71 | override fun getValue(thisRef: Any, property: KProperty<*>): Float {
72 | return preferences.getFloat(property.name, defaultValue)
73 | }
74 |
75 | override fun setValue(thisRef: Any, property: KProperty<*>, value: Float) {
76 | preferences.edit().putFloat(property.name, value).execute(synchronous)
77 | }
78 | }
79 |
80 | @SuppressLint("CommitPrefEdits")
81 | fun clearAll(synchronous: Boolean = false) {
82 | preferences.edit().clear().execute(synchronous)
83 | }
84 |
85 | fun SharedPreferences.Editor.execute(synchronous: Boolean) {
86 | if (synchronous) commit() else apply()
87 | }
88 |
89 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/utils/MD5.java:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.utils;
2 |
3 | import java.security.MessageDigest;
4 |
5 | public class MD5 {
6 | private static final char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
7 |
8 | public MD5() {
9 | }
10 |
11 | public static String hexdigest(String string) {
12 | String s = null;
13 |
14 | try {
15 | s = hexdigest(string.getBytes());
16 | } catch (Exception var3) {
17 | var3.printStackTrace();
18 | }
19 |
20 | return s;
21 | }
22 |
23 | public static String hexdigest(byte[] bytes) {
24 | String s = null;
25 |
26 | try {
27 | MessageDigest md = MessageDigest.getInstance("MD5");
28 | md.update(bytes);
29 | byte[] tmp = md.digest();
30 | char[] str = new char[32];
31 | int k = 0;
32 |
33 | for(int i = 0; i < 16; ++i) {
34 | byte byte0 = tmp[i];
35 | str[k++] = hexDigits[byte0 >>> 4 & 15];
36 | str[k++] = hexDigits[byte0 & 15];
37 | }
38 |
39 | s = new String(str);
40 | } catch (Exception var8) {
41 | var8.printStackTrace();
42 | }
43 |
44 | return s;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/utils/MainLooper.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.utils
2 |
3 | import android.os.Handler
4 | import android.os.Looper
5 |
6 | class MainLooper private constructor(looper: Looper) : Handler(looper) {
7 |
8 | fun runOnUiThread(runnable: Runnable) {
9 | if (Looper.getMainLooper() == Looper.myLooper()) {
10 | runnable.run()
11 | } else {
12 | instance.post(runnable)
13 | }
14 | }
15 |
16 | fun runOnUiThread(runnable: Runnable, delayMillis: Long) {
17 | instance.postDelayed(runnable, delayMillis)
18 | }
19 |
20 | fun isInMainThread() = Looper.myLooper() == Looper.getMainLooper()
21 |
22 | companion object {
23 | val instance = MainLooper(Looper.getMainLooper())
24 | }
25 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/utils/StarrySkyConstant.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.utils
2 |
3 | import android.annotation.SuppressLint
4 |
5 | @SuppressLint("StaticFieldLeak")
6 | object StarrySkyConstant : KtPreferences() {
7 |
8 | var KEY_CACHE_SWITCH by booleanPref()
9 | var KEY_REPEAT_MODE by stringPref()
10 |
11 | //音效相关
12 | var keyEffectSwitch by booleanPref()
13 | var keySaveEffectConfig by booleanPref()
14 | var keyEqualizerSetting by stringPref()
15 | var keyBassBoostSetting by stringPref()
16 | var keyVirtualizerSetting by stringPref()
17 | }
--------------------------------------------------------------------------------
/starrysky/src/main/java/com/lzx/starrysky/utils/TimerTaskManager.kt:
--------------------------------------------------------------------------------
1 | package com.lzx.starrysky.utils
2 |
3 | import androidx.lifecycle.Lifecycle
4 | import androidx.lifecycle.LifecycleObserver
5 | import androidx.lifecycle.OnLifecycleEvent
6 | import java.util.concurrent.Executors
7 | import java.util.concurrent.ScheduledExecutorService
8 | import java.util.concurrent.ScheduledFuture
9 | import java.util.concurrent.TimeUnit
10 |
11 | /**
12 | * 定时器
13 | */
14 | class TimerTaskManager : LifecycleObserver {
15 |
16 | companion object {
17 | private const val PROGRESS_UPDATE_INTERNAL: Long = 1000
18 | private const val PROGRESS_UPDATE_INITIAL_INTERVAL: Long = 100
19 | }
20 |
21 | private val mExecutorService: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
22 | private var mScheduleFuture: ScheduledFuture<*>? = null
23 | private var mUpdateProgressTask: Runnable? = null
24 | private var isRunning = false
25 |
26 | /**
27 | * 开始更新进度条
28 | */
29 | fun startToUpdateProgress(timeInternal: Long = PROGRESS_UPDATE_INTERNAL) {
30 | stopToUpdateProgress()
31 | if (!mExecutorService.isShutdown) {
32 | mScheduleFuture = mExecutorService.scheduleAtFixedRate({
33 | mUpdateProgressTask?.let {
34 | isRunning = true
35 | MainLooper.instance.post(it)
36 | }
37 | }, PROGRESS_UPDATE_INITIAL_INTERVAL,
38 | timeInternal,
39 | TimeUnit.MILLISECONDS)
40 | }
41 | }
42 |
43 | /**
44 | * 设置定时Runnable
45 | */
46 | fun setUpdateProgressTask(task: Runnable?) {
47 | mUpdateProgressTask = task
48 | }
49 |
50 | /**
51 | * 停止更新进度条
52 | */
53 | fun stopToUpdateProgress() {
54 | mScheduleFuture?.cancel(false)
55 | isRunning = false
56 | }
57 |
58 | /**
59 | * 释放资源
60 | */
61 | fun removeUpdateProgressTask() {
62 | stopToUpdateProgress()
63 | mExecutorService.shutdown()
64 | MainLooper.instance.removeCallbacksAndMessages(null)
65 | }
66 |
67 | /**
68 | * 是否在运行
69 | */
70 | fun isRunning() = isRunning
71 |
72 | /**
73 | * 绑定生命周期,onDestroy时自动释放
74 | */
75 | fun bindLifecycle(lifecycle: Lifecycle?) = apply {
76 | lifecycle?.removeObserver(this)
77 | lifecycle?.addObserver(this)
78 | }
79 |
80 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
81 | private fun onDestroy() {
82 | removeUpdateProgressTask()
83 | }
84 | }
--------------------------------------------------------------------------------
/starrysky/src/main/res/drawable-nodpi/default_art.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/starrysky/src/main/res/drawable-nodpi/default_art.png
--------------------------------------------------------------------------------
/starrysky/src/main/res/drawable-xxhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/starrysky/src/main/res/drawable-xxhdpi/ic_notification.png
--------------------------------------------------------------------------------
/starrysky/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/starrysky/src/main/res/drawable-xxhdpi/ic_pause_white_24dp.png
--------------------------------------------------------------------------------
/starrysky/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/starrysky/src/main/res/drawable-xxhdpi/ic_play_arrow_white_24dp.png
--------------------------------------------------------------------------------
/starrysky/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/starrysky/src/main/res/drawable-xxhdpi/ic_skip_next_white_24dp.png
--------------------------------------------------------------------------------
/starrysky/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EspoirX/StarrySky/8c6ace6df35a5f2ec4841099a5a021fa85ac9e01/starrysky/src/main/res/drawable-xxhdpi/ic_skip_previous_white_24dp.png
--------------------------------------------------------------------------------
/starrysky/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StarrySky
3 |
4 | StarrySky_Channel_ID
5 | Channel ID for StarrySky
6 |
7 | Downloads
8 |
9 | Pause
10 | Play
11 | Previous
12 | Next
13 |
14 |
--------------------------------------------------------------------------------
/starrysky/src/main/res/xml/allowed_media_browser_callers.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
56 |
57 |
58 | 19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00
59 | 70:81:1a:3e:ac:fd:2e:83:e1:8d:a9:bf:ed:e5:2d:f1:6c:e9:1f:2e:69:a4:4d:21:f1:8a:b6:69:91:13:07:71
60 | fd:b0:0c:43:db:de:8b:51:cb:31:2a:a8:1d:3b:5f:a1:77:13:ad:b9:4b:28:f5:98:d7:7f:8e:b8:9d:ac:ee:df
61 |
62 |
63 |
64 | 69:d0:72:16:9a:2c:6b:2f:5a:cc:59:0c:e4:33:a1:1a:c3:df:55:1a:df:ee:5d:5f:63:c0:83:b7:22:76:2e:19
65 | 85:cd:59:73:54:1b:e6:f4:77:d8:47:a0:bc:c6:aa:25:27:68:4b:81:9c:d5:96:85:29:66:4c:b0:71:57:b6:fe
66 |
67 |
68 |
69 | 19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00
70 |
71 |
72 |
73 | 19:75:b2:f1:71:77:bc:89:a5:df:f3:1f:9e:64:a6:ca:e2:81:a5:3d:c1:d1:d5:9b:1d:14:7f:e1:c8:2a:fa:00
74 | f0:fd:6c:5b:41:0f:25:cb:25:c3:b5:33:46:c8:97:2f:ae:30:f8:ee:74:11:df:91:04:80:ad:6b:2d:60:db:83
75 |
76 |
--------------------------------------------------------------------------------
/starrysky/src/main/res/xml/automotive_app_desc.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/starrysky/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------