Skip to content

Commit

Permalink
Merge pull request #570 from lovegaoshi/dev
Browse files Browse the repository at this point in the history
feat: ACFun and widget
  • Loading branch information
lovegaoshi authored Oct 11, 2024
2 parents 4254646 + a833829 commit 70c7d32
Show file tree
Hide file tree
Showing 43 changed files with 877 additions and 404 deletions.
10 changes: 10 additions & 0 deletions __tests__/mediafetch/acfunVideo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import fetcher from '../../src/utils/mediafetch/acfunvideo';

test('acfunvideo', async () => {
const content = await fetcher.regexFetch({
reExtracted: fetcher.regexSearchMatch.exec(
'https://www.acfun.cn/v/ac46370925'
)!,
});
expect(content?.songList[0]?.id).not.toBeUndefined();
});
2 changes: 2 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ android {

buildFeatures {
buildConfig = true
viewBinding true
}

compileOptions {
Expand Down Expand Up @@ -184,6 +185,7 @@ dependencies {

// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation("androidx.media3:media3-session:1.4.1")

if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
Expand Down
124 changes: 75 additions & 49 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,86 +1,112 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />

<!--com.github.yyued:SVGAPlayer-Android:2.6.1 has allowBackup = true; doing tools:replace below. -->
<application
android:usesCleartextTraffic="false"
tools:replace="android:allowBackup"
android:name="com.noxplay.noxplayer.MainApplication"
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />

<application
android:name=".MainApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:largeHeap="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:supportsRtl="true">
<profileable android:shell="true"
tools:targetApi="q" />

<receiver android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
android:usesCleartextTraffic="false"
tools:replace="android:allowBackup">
<receiver
android:name=".APMWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/a_p_m_widget_info" />
</receiver>

<profileable
android:shell="true"
tools:targetApi="q" />

<receiver
android:name="androidx.media.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.noxplay.noxplayer.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
android:name="androidx.core.content.FileProvider"
android:authorities="com.noxplay.noxplayer.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>

<activity
android:name="com.noxplay.noxplayer.MainActivity"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:name=".MainActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:networkSecurityConfig="@xml/network_security_config"
android:exported="true">
<intent-filter>
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />

<category android:name="android.intent.category.DEFAULT" />

<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />

<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>

<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name="net.openid.appauth.RedirectUriReceiverActivity"
tools:node="replace"
android:exported="true">
android:exported="true"
tools:node="replace">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="com.noxplayer"/>
<data android:scheme="com.googleusercontent.apps.369249578889-cvr47cn786i0scicobhlljidu1b32572"/>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="com.noxplayer" />
<data android:scheme="com.googleusercontent.apps.369249578889-cvr47cn786i0scicobhlljidu1b32572" />
</intent-filter>
</activity>

<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc"/>
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
</application>

</manifest>
155 changes: 155 additions & 0 deletions android/app/src/main/java/com/noxplay/noxplayer/APMWidget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package com.noxplay.noxplayer

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.util.Log
import android.widget.RemoteViews
import com.doublesymmetry.trackplayer.model.Track
import com.doublesymmetry.trackplayer.module.MusicEvents
import com.doublesymmetry.trackplayer.service.MusicService
import com.lovegaoshi.kotlinaudio.models.AudioPlayerState

/**
* Implementation of App Widget functionality.
*/
class APMWidget : AppWidgetProvider() {

private lateinit var binder: MusicService.MusicBinder
private var currentTrack: Track? = null

private fun bindService (context: Context?): Boolean {
if (!::binder.isInitialized || !binder.isBinderAlive) {
if (context == null) return false
Log.d("APM", "widget attempt to connect to MusicService...")
val mBinder = peekService(context, Intent(context, MusicService::class.java)) ?: return false
Log.d("APM", "widget attempt to cast to MusicService...")
binder = mBinder as MusicService.MusicBinder
if (!binder.isBinderAlive) return false
}
return true
}

private fun emit(context: Context?, e: String) {
if (!bindService(context)) return
binder.service.emit(e)
}

private fun initWidget(views: RemoteViews, context: Context): RemoteViews {
views.setOnClickPendingIntent(
R.id.buttonPrev, PendingIntent.getBroadcast(
context, 0,
Intent(context, APMWidget::class.java).setAction(MusicEvents.BUTTON_SKIP_PREVIOUS),
PendingIntent.FLAG_IMMUTABLE))
views.setOnClickPendingIntent(
R.id.buttonPlay, PendingIntent.getBroadcast(
context, 0,
Intent(context, APMWidget::class.java).setAction(MusicEvents.BUTTON_PLAY_PAUSE),
PendingIntent.FLAG_IMMUTABLE))
views.setOnClickPendingIntent(
R.id.buttonNext, PendingIntent.getBroadcast(
context, 0,
Intent(context, APMWidget::class.java).setAction(MusicEvents.BUTTON_SKIP_NEXT),
PendingIntent.FLAG_IMMUTABLE))
views.setOnClickPendingIntent(
R.id.APMWidget, PendingIntent.getBroadcast(
context, 0,
Intent(context, APMWidget::class.java).setAction(WIDGET_CLICK),
PendingIntent.FLAG_IMMUTABLE))
return views
}

private fun updateTrack(views: RemoteViews, track: Track?, bitmap: Bitmap?): RemoteViews {
if (track == null) {
views.setTextViewCompoundDrawables(
R.id.buttonPlay, R.drawable.media3_icon_play,0,0,0)
}
views.setTextViewText(R.id.songName, track?.title ?: "")
views.setTextViewText(R.id.artistName, track?.artist ?: "")
views.setImageViewBitmap(R.id.albumArt, bitmap)
return views
}

private fun updatePlayPause(views: RemoteViews) {
val isPlaying = binder.service.state === AudioPlayerState.PLAYING
views.setTextViewCompoundDrawables(
R.id.buttonPlay,
if (isPlaying) R.drawable.media3_icon_pause else R.drawable.media3_icon_play,
0,0,0)
}

// HACK: properly abstract all of these
private fun clearWidgetContent(context: Context?) {
if (context == null) return
val widgetManager = AppWidgetManager.getInstance(context)
val ids = widgetManager.getAppWidgetIds(ComponentName(context, APMWidget::class.java))
val views = RemoteViews(context.packageName, R.layout.a_p_m_widget)
updateTrack(views, null, null)
ids.forEach { id -> widgetManager.updateAppWidget(id, views) }
// onUpdate(context, widgetManager, ids)
}

override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// Construct the RemoteViews object
val views = RemoteViews(context.packageName, R.layout.a_p_m_widget)
if (!bindService(context)) {
updateTrack(views, null, null)
} else {
initWidget(views, context)
updatePlayPause(views)
val track = binder.service.currentTrack
if (track != currentTrack) {
currentTrack = track
val bitmap = if (binder.service.currentBitmap.size == 1) binder.service.currentBitmap[0] else null
updateTrack(views, currentTrack, bitmap)
}
}
// Instruct the widget manager to update the widget
for (appWidgetId in appWidgetIds) {
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}

override fun onReceive(context: Context?, intent: Intent?) {
try {
when (intent?.action) {
"clear-widget" -> clearWidgetContent(context)
WIDGET_CLICK -> {
if (!bindService(context)) {
Intent(context, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also {i -> context?.startActivity(i)}
return
}
}
MusicEvents.BUTTON_SKIP_PREVIOUS -> emit(context, MusicEvents.BUTTON_SKIP_PREVIOUS)
MusicEvents.BUTTON_SKIP_NEXT -> emit(context, MusicEvents.BUTTON_SKIP_NEXT)
MusicEvents.BUTTON_PLAY_PAUSE -> emit(context, MusicEvents.BUTTON_PLAY_PAUSE)
else -> {}
}
} catch (e: Exception) {
Log.w("APM", "widget action ${intent?.action} failed by $e")

}
super.onReceive(context, intent)
}

override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}

override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
}

const val WIDGET_CLICK = "widget-click"
const val WIDGET_CLEAR = "clear-widget"
24 changes: 24 additions & 0 deletions android/app/src/main/java/com/noxplay/noxplayer/APMWidgetModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.noxplay.noxplayer

import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.util.Log
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

class APMWidgetModule (private val reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName() = "APMWidgetModule"

@ReactMethod fun updateWidget() {
val intent = Intent(reactContext, APMWidget::class.java)
intent.setAction("android.appwidget.action.APPWIDGET_UPDATE")
val widgetManager = AppWidgetManager.getInstance(reactContext)
val ids = widgetManager.getAppWidgetIds(ComponentName(reactContext, APMWidget::class.java))
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
reactContext.sendBroadcast(intent)
}
}
Loading

0 comments on commit 70c7d32

Please sign in to comment.