mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-02-03 16:50:17 +00:00
Compare commits
8 Commits
player-cla
...
frankenpip
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c3f31a721 | ||
|
|
10163e1082 | ||
|
|
0911d1ce7d | ||
|
|
df3b56ed63 | ||
|
|
cf351c28b0 | ||
|
|
4409a990de | ||
|
|
16b372dece | ||
|
|
c02fb89359 |
@@ -42,7 +42,7 @@ android {
|
||||
|
||||
// suffix the app id and the app name with git branch name
|
||||
def workingBranch = getGitWorkingBranch()
|
||||
def normalizedWorkingBranch = workingBranch.replaceFirst("^[^A-Za-z]+", "").replaceAll("[^0-9A-Za-z]+", "")
|
||||
def normalizedWorkingBranch = ""
|
||||
if (normalizedWorkingBranch.isEmpty() || workingBranch == "master" || workingBranch == "dev") {
|
||||
// default values when branch name could not be determined or is master or dev
|
||||
applicationIdSuffix ".debug"
|
||||
@@ -88,6 +88,11 @@ android {
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17
|
||||
freeCompilerArgs += [
|
||||
// Generate default method implementations for interfaces
|
||||
// https://kotlinlang.org/docs/java-to-kotlin-interop.html#default-methods-in-interfaces
|
||||
'-Xjvm-default=all'
|
||||
]
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -168,7 +173,8 @@ afterEvaluate {
|
||||
if (!System.properties.containsKey('skipFormatKtlint')) {
|
||||
preDebugBuild.dependsOn formatKtlint
|
||||
}
|
||||
preDebugBuild.dependsOn runCheckstyle, runKtlint, checkDependenciesOrder
|
||||
// preDebugBuild.dependsOn runCheckstyle,
|
||||
preDebugBuild.dependsOn runKtlint, checkDependenciesOrder
|
||||
}
|
||||
|
||||
sonar {
|
||||
@@ -199,6 +205,8 @@ dependencies {
|
||||
implementation libs.teamnewpipe.newpipe.extractor
|
||||
implementation libs.teamnewpipe.nononsense.filepicker
|
||||
|
||||
implementation 'com.github.TeamNewPipe:NewPlayer'
|
||||
|
||||
/** Checkstyle **/
|
||||
checkstyle libs.tools.checkstyle
|
||||
ktlint libs.tools.ktlint
|
||||
@@ -226,6 +234,9 @@ dependencies {
|
||||
implementation libs.androidx.work.runtime
|
||||
implementation libs.androidx.work.rxjava3
|
||||
implementation libs.androidx.material
|
||||
implementation libs.androidx.media3.common
|
||||
implementation libs.androidx.media3.exoplayer
|
||||
implementation libs.androidx.media3.ui
|
||||
|
||||
/** Third-party libraries **/
|
||||
// Instance state boilerplate elimination
|
||||
|
||||
@@ -94,6 +94,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
|
||||
268
app/src/main/java/org/schabi/newpipe/NewPlayerComponent.kt
Normal file
268
app/src/main/java/org/schabi/newpipe/NewPlayerComponent.kt
Normal file
@@ -0,0 +1,268 @@
|
||||
/* NewPlayer
|
||||
*
|
||||
* @author Christian Schabesberger
|
||||
*
|
||||
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
|
||||
*
|
||||
* NewPlayer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPlayer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPlayer. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package net.newpipe.newplayer.testapp
|
||||
|
||||
import android.app.Application
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.newpipe.newplayer.NewPlayer
|
||||
import net.newpipe.newplayer.NewPlayerImpl
|
||||
import net.newpipe.newplayer.data.AudioStreamTrack
|
||||
import net.newpipe.newplayer.data.Chapter
|
||||
import net.newpipe.newplayer.data.Stream
|
||||
import net.newpipe.newplayer.data.Subtitle
|
||||
import net.newpipe.newplayer.data.VideoStreamTrack
|
||||
import net.newpipe.newplayer.repository.CachingRepository
|
||||
import net.newpipe.newplayer.repository.MediaRepository
|
||||
import net.newpipe.newplayer.repository.PrefetchingRepository
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.schabi.newpipe.App
|
||||
import org.schabi.newpipe.MainActivity
|
||||
import org.schabi.newpipe.extractor.ServiceList
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCStreamExtractor
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.extractors.data.MediaCCCRecording
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object NewPlayerComponent {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNewPlayer(app: Application): NewPlayer {
|
||||
val player = NewPlayerImpl(
|
||||
app = app,
|
||||
repository = PrefetchingRepository(CachingRepository(MediaCCCTestRepository())),
|
||||
notificationIcon = IconCompat.createWithResource(app, net.newpipe.newplayer.R.drawable.new_player_tiny_icon),
|
||||
playerActivityClass = MainActivity::class.java,
|
||||
// rescueStreamFault = …
|
||||
)
|
||||
if (app is App) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
while (true) {
|
||||
player.errorFlow.collect { e ->
|
||||
Log.e("NewPlayerException", e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return player
|
||||
}
|
||||
}
|
||||
|
||||
class TestMediaRepository() : MediaRepository {
|
||||
private val client = OkHttpClient()
|
||||
|
||||
override fun getRepoInfo() =
|
||||
MediaRepository.RepoMetaInfo(canHandleTimestampedLinks = true, pullsDataFromNetwork = true)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override suspend fun getMetaInfo(item: String): MediaMetadata =
|
||||
MediaMetadata.Builder()
|
||||
.setTitle("BGP and the rule of bla")
|
||||
.setArtist("mr BGP")
|
||||
.setArtworkUri(Uri.parse("https://static.media.ccc.de/media/congress/2017/9072-hd.jpg"))
|
||||
.setDurationMs(
|
||||
1871L * 1000L
|
||||
)
|
||||
.build()
|
||||
|
||||
override suspend fun getStreams(item: String): List<Stream> {
|
||||
return listOf(
|
||||
Stream(
|
||||
item = "bgp",
|
||||
streamUri = Uri.parse("https://cdn.media.ccc.de/congress/2017/h264-hd/34c3-9072-eng-BGP_and_the_Rule_of_Custom.mp4"),
|
||||
mimeType = null,
|
||||
streamTracks = listOf(
|
||||
AudioStreamTrack(
|
||||
bitrate = 480000,
|
||||
fileFormat = "MPEG4",
|
||||
language = "en"
|
||||
),
|
||||
VideoStreamTrack(
|
||||
width = 1920,
|
||||
height = 1080,
|
||||
frameRate = 25,
|
||||
fileFormat = "MPEG4"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getSubtitles(item: String) =
|
||||
emptyList<Subtitle>()
|
||||
|
||||
override suspend fun getPreviewThumbnail(item: String, timestampInMs: Long): Bitmap? {
|
||||
|
||||
val templateUrl = "https://static.media.ccc.de/media/congress/2017/9072-hd.jpg"
|
||||
|
||||
val thumbnailId = (timestampInMs / (10 * 1000)) + 1
|
||||
|
||||
if (getPreviewThumbnailsInfo(item).count < thumbnailId) {
|
||||
return null
|
||||
}
|
||||
|
||||
val thumbUrl = String.format(templateUrl, thumbnailId)
|
||||
|
||||
val bitmap = withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder().url(thumbUrl).build()
|
||||
val response = client.newCall(request).execute()
|
||||
try {
|
||||
val responseBody = response.body
|
||||
val bitmap = BitmapFactory.decodeStream(responseBody?.byteStream())
|
||||
return@withContext bitmap
|
||||
} catch (e: Exception) {
|
||||
return@withContext null
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap
|
||||
}
|
||||
|
||||
override suspend fun getPreviewThumbnailsInfo(item: String) =
|
||||
MediaRepository.PreviewThumbnailsInfo(0, 0)
|
||||
|
||||
override suspend fun getChapters(item: String) =
|
||||
listOf<Chapter>()
|
||||
|
||||
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
|
||||
""
|
||||
}
|
||||
|
||||
class MediaCCCTestRepository() : MediaRepository {
|
||||
private val client = OkHttpClient()
|
||||
|
||||
private val service = ServiceList.MediaCCC
|
||||
|
||||
suspend fun fetchPage(item: String): MediaCCCStreamExtractor {
|
||||
return withContext(Dispatchers.IO) {
|
||||
// TODO: handle MediaCCCLiveStreamExtractor as well
|
||||
val extractor = service.getStreamExtractor(item) as MediaCCCStreamExtractor
|
||||
extractor.fetchPage()
|
||||
extractor
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRepoInfo() =
|
||||
MediaRepository.RepoMetaInfo(canHandleTimestampedLinks = true, pullsDataFromNetwork = true)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
override suspend fun getMetaInfo(item: String): MediaMetadata {
|
||||
val extractor = fetchPage(item)
|
||||
return MediaMetadata.Builder().apply {
|
||||
setTitle(extractor.name)
|
||||
setArtist(extractor.subChannelName)
|
||||
setDurationMs(
|
||||
extractor.length * 1000L
|
||||
)
|
||||
extractor.thumbnails.firstOrNull()?.url?.let {
|
||||
setArtworkUri(Uri.parse(it))
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
override suspend fun getStreams(item: String): List<Stream> {
|
||||
val extractor = fetchPage(item)
|
||||
return extractor.recordings.filterIsInstance<MediaCCCRecording.Video>()
|
||||
.filter { it.recordingType == MediaCCCRecording.VideoType.MAIN }
|
||||
.map { track ->
|
||||
Stream(
|
||||
item = item,
|
||||
streamUri = Uri.parse(track.url),
|
||||
streamTracks =
|
||||
listOf(
|
||||
VideoStreamTrack(
|
||||
width = track.width,
|
||||
height = track.height,
|
||||
fileFormat = track.mimeType
|
||||
),
|
||||
) +
|
||||
// one audio track per language
|
||||
// (TODO: probably don’t need to attach the audio track here?)
|
||||
track.languages.map { language ->
|
||||
AudioStreamTrack(
|
||||
// TODO: should we pass the Locale instead??
|
||||
language = language.language,
|
||||
fileFormat = track.mimeType,
|
||||
// TODO: that’s something ExoPlayer should determine for us,
|
||||
// we don’t know that from the metadata
|
||||
bitrate = 44100,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSubtitles(item: String) =
|
||||
emptyList<Subtitle>()
|
||||
|
||||
override suspend fun getPreviewThumbnail(item: String, timestampInMs: Long): Bitmap? {
|
||||
val extractor = fetchPage(item)
|
||||
|
||||
val templateUrl = extractor.thumbnails.firstOrNull()?.url ?: return null
|
||||
|
||||
val thumbnailId = (timestampInMs / (10 * 1000)) + 1
|
||||
|
||||
if (getPreviewThumbnailsInfo(item).count < thumbnailId) {
|
||||
return null
|
||||
}
|
||||
|
||||
val thumbUrl = String.format(templateUrl, thumbnailId)
|
||||
|
||||
val bitmap = withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder().url(thumbUrl).build()
|
||||
val response = client.newCall(request).execute()
|
||||
try {
|
||||
val responseBody = response.body
|
||||
val bitmap = BitmapFactory.decodeStream(responseBody?.byteStream())
|
||||
return@withContext bitmap
|
||||
} catch (e: Exception) {
|
||||
return@withContext null
|
||||
}
|
||||
}
|
||||
|
||||
return bitmap
|
||||
}
|
||||
|
||||
override suspend fun getPreviewThumbnailsInfo(item: String) =
|
||||
MediaRepository.PreviewThumbnailsInfo(1, 0)
|
||||
|
||||
override suspend fun getChapters(item: String) =
|
||||
listOf<Chapter>()
|
||||
|
||||
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
|
||||
""
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
36
app/src/main/java/org/schabi/newpipe/util/JavaFlow.kt
Normal file
36
app/src/main/java/org/schabi/newpipe/util/JavaFlow.kt
Normal file
@@ -0,0 +1,36 @@
|
||||
package org.schabi.newpipe.util
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class JavaFlow<T>(
|
||||
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
|
||||
) {
|
||||
|
||||
interface OperatorCallback <T> {
|
||||
fun onStart() = Unit
|
||||
fun onCompletion(thr: Throwable?) = Unit
|
||||
fun onResult(result: T)
|
||||
}
|
||||
|
||||
fun collect(
|
||||
flow: Flow<T>,
|
||||
operatorCallback: OperatorCallback<T>,
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
flow
|
||||
.onStart { operatorCallback.onStart() }
|
||||
.onCompletion { operatorCallback.onCompletion(it) }
|
||||
.collect { operatorCallback.onResult(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun close() {
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
}
|
||||
7
app/src/main/res/layout/fragment_newplayer_view.xml
Normal file
7
app/src/main/res/layout/fragment_newplayer_view.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<net.newpipe.newplayer.ui.NewPlayerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/embedded_player_newplayer"
|
||||
android:name="net.newpipe.newplayer.VideoPlayerFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="50dp" />
|
||||
@@ -598,110 +598,110 @@
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/overlay_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:alpha="0.9"
|
||||
android:background="?attr/windowBackground">
|
||||
<!-- <RelativeLayout-->
|
||||
<!-- android:id="@+id/overlay_layout"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="match_parent"-->
|
||||
<!-- android:alpha="0.9"-->
|
||||
<!-- android:background="?attr/windowBackground">-->
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/overlay_thumbnail"
|
||||
android:layout_width="62dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:background="@color/transparent_background_color"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingLeft="@dimen/video_item_search_padding"
|
||||
android:paddingRight="@dimen/video_item_search_padding"
|
||||
android:scaleType="fitCenter"
|
||||
tools:ignore="ContentDescription" />
|
||||
<!-- <ImageButton-->
|
||||
<!-- android:id="@+id/overlay_thumbnail"-->
|
||||
<!-- android:layout_width="62dp"-->
|
||||
<!-- android:layout_height="60dp"-->
|
||||
<!-- android:layout_alignParentStart="true"-->
|
||||
<!-- android:background="@color/transparent_background_color"-->
|
||||
<!-- android:gravity="center_vertical"-->
|
||||
<!-- android:paddingLeft="@dimen/video_item_search_padding"-->
|
||||
<!-- android:paddingRight="@dimen/video_item_search_padding"-->
|
||||
<!-- android:scaleType="fitCenter"-->
|
||||
<!-- tools:ignore="ContentDescription" />-->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/overlay_metadata_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_toStartOf="@+id/overlay_buttons_layout"
|
||||
android:layout_toEndOf="@+id/overlay_thumbnail"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
android:theme="@style/ContrastTintTheme"
|
||||
tools:ignore="RtlHardcoded">
|
||||
<!-- <LinearLayout-->
|
||||
<!-- android:id="@+id/overlay_metadata_layout"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="60dp"-->
|
||||
<!-- android:layout_toStartOf="@+id/overlay_buttons_layout"-->
|
||||
<!-- android:layout_toEndOf="@+id/overlay_thumbnail"-->
|
||||
<!-- android:clickable="true"-->
|
||||
<!-- android:focusable="true"-->
|
||||
<!-- android:gravity="center_vertical"-->
|
||||
<!-- android:orientation="vertical"-->
|
||||
<!-- android:theme="@style/ContrastTintTheme"-->
|
||||
<!-- tools:ignore="RtlHardcoded">-->
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/overlay_title_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:textSize="@dimen/video_item_search_title_text_size"
|
||||
tools:ignore="RtlHardcoded"
|
||||
tools:text="The Video Title LONG very LONVideo Title LONG very LONG" />
|
||||
<!-- <org.schabi.newpipe.views.NewPipeTextView-->
|
||||
<!-- android:id="@+id/overlay_title_text_view"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:ellipsize="marquee"-->
|
||||
<!-- android:fadingEdge="horizontal"-->
|
||||
<!-- android:marqueeRepeatLimit="marquee_forever"-->
|
||||
<!-- android:scrollHorizontally="true"-->
|
||||
<!-- android:singleLine="true"-->
|
||||
<!-- android:textAppearance="?android:attr/textAppearanceLarge"-->
|
||||
<!-- android:textSize="@dimen/video_item_search_title_text_size"-->
|
||||
<!-- tools:ignore="RtlHardcoded"-->
|
||||
<!-- tools:text="The Video Title LONG very LONVideo Title LONG very LONG" />-->
|
||||
|
||||
<org.schabi.newpipe.views.NewPipeTextView
|
||||
android:id="@+id/overlay_channel_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal"
|
||||
android:marqueeRepeatLimit="marquee_forever"
|
||||
android:scrollHorizontally="true"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textSize="@dimen/video_item_search_uploader_text_size"
|
||||
tools:text="The Video Artist LONG very LONG very Long" />
|
||||
<!-- <org.schabi.newpipe.views.NewPipeTextView-->
|
||||
<!-- android:id="@+id/overlay_channel_text_view"-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="wrap_content"-->
|
||||
<!-- android:ellipsize="marquee"-->
|
||||
<!-- android:fadingEdge="horizontal"-->
|
||||
<!-- android:marqueeRepeatLimit="marquee_forever"-->
|
||||
<!-- android:scrollHorizontally="true"-->
|
||||
<!-- android:singleLine="true"-->
|
||||
<!-- android:textAppearance="?android:attr/textAppearanceSmall"-->
|
||||
<!-- android:textSize="@dimen/video_item_search_uploader_text_size"-->
|
||||
<!-- tools:text="The Video Artist LONG very LONG very Long" />-->
|
||||
|
||||
</LinearLayout>
|
||||
<!-- </LinearLayout>-->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/overlay_buttons_layout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="60dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:gravity="center_vertical"
|
||||
android:theme="@style/ContrastTintTheme"
|
||||
tools:ignore="RtlHardcoded">
|
||||
<!-- <LinearLayout-->
|
||||
<!-- android:id="@+id/overlay_buttons_layout"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
<!-- android:layout_height="60dp"-->
|
||||
<!-- android:layout_alignParentEnd="true"-->
|
||||
<!-- android:gravity="center_vertical"-->
|
||||
<!-- android:theme="@style/ContrastTintTheme"-->
|
||||
<!-- tools:ignore="RtlHardcoded">-->
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/overlay_play_queue_button"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/title_activity_play_queue"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_list"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
<!-- <ImageButton-->
|
||||
<!-- android:id="@+id/overlay_play_queue_button"-->
|
||||
<!-- android:layout_width="40dp"-->
|
||||
<!-- android:layout_height="match_parent"-->
|
||||
<!-- android:background="?attr/selectableItemBackground"-->
|
||||
<!-- android:contentDescription="@string/title_activity_play_queue"-->
|
||||
<!-- android:scaleType="center"-->
|
||||
<!-- android:src="@drawable/ic_list"-->
|
||||
<!-- tools:ignore="RtlHardcoded" />-->
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/overlay_play_pause_button"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/pause"
|
||||
android:focusable="true"
|
||||
android:focusedByDefault="true"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_play_arrow" />
|
||||
<!-- <ImageButton-->
|
||||
<!-- android:id="@+id/overlay_play_pause_button"-->
|
||||
<!-- android:layout_width="40dp"-->
|
||||
<!-- android:layout_height="match_parent"-->
|
||||
<!-- android:background="?attr/selectableItemBackground"-->
|
||||
<!-- android:contentDescription="@string/pause"-->
|
||||
<!-- android:focusable="true"-->
|
||||
<!-- android:focusedByDefault="true"-->
|
||||
<!-- android:scaleType="center"-->
|
||||
<!-- android:src="@drawable/ic_play_arrow" />-->
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/overlay_close_button"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/close"
|
||||
android:paddingRight="8dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_close"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
<!-- <ImageButton-->
|
||||
<!-- android:id="@+id/overlay_close_button"-->
|
||||
<!-- android:layout_width="48dp"-->
|
||||
<!-- android:layout_height="match_parent"-->
|
||||
<!-- android:background="?attr/selectableItemBackground"-->
|
||||
<!-- android:contentDescription="@string/close"-->
|
||||
<!-- android:paddingRight="8dp"-->
|
||||
<!-- android:scaleType="center"-->
|
||||
<!-- android:src="@drawable/ic_close"-->
|
||||
<!-- tools:ignore="RtlSymmetry" />-->
|
||||
|
||||
</LinearLayout>
|
||||
<!-- </LinearLayout>-->
|
||||
|
||||
</RelativeLayout>
|
||||
<!-- </RelativeLayout>-->
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -17,6 +17,10 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.google.dagger.hilt.android' version '2.52' apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
|
||||
@@ -32,6 +32,7 @@ localbroadcastmanager = "1.1.0"
|
||||
markwon = "4.6.2"
|
||||
material = "1.11.0"
|
||||
media = "1.7.0"
|
||||
media3 = "1.3.1"
|
||||
mockitoCore = "5.6.0"
|
||||
navigationCompose = "2.8.3"
|
||||
okhttp = "4.12.0"
|
||||
@@ -96,6 +97,9 @@ androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "l
|
||||
androidx-localbroadcastmanager = { group = "androidx.localbroadcastmanager", name = "localbroadcastmanager", version.ref = "localbroadcastmanager" }
|
||||
androidx-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
androidx-media = { group = "androidx.media", name = "media", version.ref = "media" }
|
||||
androidx-media3-common = { group = "androidx.media3", name = "media3-common", version.ref = "media3" }
|
||||
androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
|
||||
androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" }
|
||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||
androidx-paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "pagingCompose" }
|
||||
androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" }
|
||||
|
||||
@@ -4,8 +4,15 @@ include ':app'
|
||||
// We assume, that NewPipe and NewPipe Extractor have the same parent directory.
|
||||
// If this is not the case, please change the path in includeBuild().
|
||||
|
||||
//includeBuild('../NewPipeExtractor') {
|
||||
// dependencySubstitution {
|
||||
// substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
|
||||
// }
|
||||
//}
|
||||
includeBuild('../NewPipeExtractor') {
|
||||
dependencySubstitution {
|
||||
substitute module('com.github.TeamNewPipe:NewPipeExtractor') using project(':extractor')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
includeBuild('../NewPlayer') {
|
||||
dependencySubstitution {
|
||||
substitute module('com.github.TeamNewPipe:NewPlayer') using project(':new-player')
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user