diff --git a/app/src/main/java/com/github/libretube/activities/MainActivity.kt b/app/src/main/java/com/github/libretube/activities/MainActivity.kt index d2e9f2b6b..aac8d554a 100644 --- a/app/src/main/java/com/github/libretube/activities/MainActivity.kt +++ b/app/src/main/java/com/github/libretube/activities/MainActivity.kt @@ -191,6 +191,11 @@ class MainActivity : AppCompatActivity() { return true } }) + + searchView.setOnCloseListener { + onBackPressed() + true + } return super.onCreateOptionsMenu(menu) } diff --git a/app/src/main/java/com/github/libretube/adapters/PlaylistAdapter.kt b/app/src/main/java/com/github/libretube/adapters/PlaylistAdapter.kt index a0a8eded0..74e44b6a6 100644 --- a/app/src/main/java/com/github/libretube/adapters/PlaylistAdapter.kt +++ b/app/src/main/java/com/github/libretube/adapters/PlaylistAdapter.kt @@ -68,44 +68,31 @@ class PlaylistAdapter( if (isOwner) { deletePlaylist.visibility = View.VISIBLE deletePlaylist.setOnClickListener { - val token = PreferenceHelper.getToken() - removeFromPlaylist(token, position) + removeFromPlaylist(position) } } watchProgress.setWatchProgressLength(videoId, streamItem.duration!!) } } - private fun removeFromPlaylist(token: String, position: Int) { - fun run() { - CoroutineScope(Dispatchers.IO).launch { - val response = try { - RetrofitInstance.authApi.removeFromPlaylist( - token, - PlaylistId(playlistId = playlistId, index = position) - ) - } catch (e: IOException) { - println(e) - Log.e(TAG, "IOException, you might not have internet connection") - return@launch - } catch (e: HttpException) { - Log.e(TAG, "HttpException, unexpected response") - return@launch - } finally { - } - try { - if (response.message == "ok") { - Log.d(TAG, "deleted!") - videoFeed.removeAt(position) - // FIXME: This needs to run on UI thread? - activity.runOnUiThread { notifyDataSetChanged() } - } - } catch (e: Exception) { - Log.e(TAG, e.toString()) - } + fun removeFromPlaylist(position: Int) { + videoFeed.removeAt(position) + activity.runOnUiThread { notifyDataSetChanged() } + CoroutineScope(Dispatchers.IO).launch { + try { + RetrofitInstance.authApi.removeFromPlaylist( + PreferenceHelper.getToken(), + PlaylistId(playlistId = playlistId, index = position) + ) + } catch (e: IOException) { + println(e) + Log.e(TAG, "IOException, you might not have internet connection") + return@launch + } catch (e: HttpException) { + Log.e(TAG, "HttpException, unexpected response") + return@launch } } - run() } } diff --git a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt index 506d05e5c..5d2ff0739 100644 --- a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt @@ -18,7 +18,6 @@ import android.os.Looper import android.os.PowerManager import android.support.v4.media.session.MediaSessionCompat import android.text.Html -import android.text.TextUtils import android.text.format.DateUtils import android.util.Log import android.view.LayoutInflater @@ -35,6 +34,7 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import com.fasterxml.jackson.databind.ObjectMapper import com.github.libretube.BACKGROUND_CHANNEL_ID import com.github.libretube.Globals import com.github.libretube.PLAYER_NOTIFICATION_ID @@ -53,7 +53,6 @@ import com.github.libretube.obj.ChapterSegment import com.github.libretube.obj.Playlist import com.github.libretube.obj.Segment import com.github.libretube.obj.Segments -import com.github.libretube.obj.SponsorBlockPrefs import com.github.libretube.obj.StreamItem import com.github.libretube.obj.Streams import com.github.libretube.obj.Subscribe @@ -157,7 +156,6 @@ class PlayerFragment : Fragment() { private var token = "" private var relatedStreamsEnabled = true private var autoplayEnabled = false - private val sponsorBlockPrefs = SponsorBlockPrefs() private var autoRotationEnabled = true private var playbackSpeed = "1F" private var pausePlayerOnScreenOffEnabled = false @@ -171,6 +169,8 @@ class PlayerFragment : Fragment() { private var bufferingGoal = 50000 private var seekBarPreview = false private var defaultSubtitle = "" + private var sponsorBlockEnabled = true + private var sponsorBlockNotifications = true /** * for autoplay @@ -234,7 +234,6 @@ class PlayerFragment : Fragment() { mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT } - setSponsorBlockPrefs() createExoPlayer() initializeTransitionLayout() initializeOnClickActions() @@ -315,6 +314,16 @@ class PlayerFragment : Fragment() { false ) + sponsorBlockEnabled = PreferenceHelper.getBoolean( + "sb_enabled_key", + true + ) + + sponsorBlockNotifications = PreferenceHelper.getBoolean( + "sb_notifications_key", + true + ) + defaultSubtitle = PreferenceHelper.getString( PreferenceKeys.DEFAULT_SUBTITLE, "" @@ -325,29 +334,6 @@ class PlayerFragment : Fragment() { } } - private fun setSponsorBlockPrefs() { - sponsorBlockPrefs.sponsorBlockEnabled = - PreferenceHelper.getBoolean("sb_enabled_key", true) - sponsorBlockPrefs.sponsorNotificationsEnabled = - PreferenceHelper.getBoolean("sb_notifications_key", true) - sponsorBlockPrefs.introEnabled = - PreferenceHelper.getBoolean("intro_category_key", false) - sponsorBlockPrefs.selfPromoEnabled = - PreferenceHelper.getBoolean("selfpromo_category_key", false) - sponsorBlockPrefs.interactionEnabled = - PreferenceHelper.getBoolean("interaction_category_key", false) - sponsorBlockPrefs.sponsorsEnabled = - PreferenceHelper.getBoolean("sponsors_category_key", true) - sponsorBlockPrefs.outroEnabled = - PreferenceHelper.getBoolean("outro_category_key", false) - sponsorBlockPrefs.fillerEnabled = - PreferenceHelper.getBoolean("filler_category_key", false) - sponsorBlockPrefs.musicOffTopicEnabled = - PreferenceHelper.getBoolean("music_offtopic_category_key", false) - sponsorBlockPrefs.previewEnabled = - PreferenceHelper.getBoolean("preview_category_key", false) - } - private fun initializeTransitionLayout() { val mainActivity = activity as MainActivity mainActivity.binding.container.visibility = View.VISIBLE @@ -729,20 +715,20 @@ class PlayerFragment : Fragment() { } private fun checkForSegments() { - if (!exoPlayer.isPlaying || !sponsorBlockPrefs.sponsorBlockEnabled) return + if (!exoPlayer.isPlaying || !sponsorBlockEnabled) return - exoPlayerView.postDelayed(this::checkForSegments, 100) + Handler(Looper.getMainLooper()).postDelayed(this::checkForSegments, 100) if (!::segmentData.isInitialized || segmentData.segments.isEmpty()) { return } segmentData.segments.forEach { segment: Segment -> - val segmentStart = (segment.segment!![0] * 1000.0f).toLong() - val segmentEnd = (segment.segment[1] * 1000.0f).toLong() + val segmentStart = (segment.segment!![0] * 1000f).toLong() + val segmentEnd = (segment.segment[1] * 1000f).toLong() val currentPosition = exoPlayer.currentPosition if (currentPosition in segmentStart until segmentEnd) { - if (sponsorBlockPrefs.sponsorNotificationsEnabled) { + if (sponsorBlockNotifications) { Toast.makeText(context, R.string.segment_skipped, Toast.LENGTH_SHORT).show() } exoPlayer.seekTo(segmentEnd) @@ -784,7 +770,7 @@ class PlayerFragment : Fragment() { exoPlayer.play() exoPlayerView.useController = true initializePlayerNotification(requireContext()) - fetchSponsorBlockSegments() + if (sponsorBlockEnabled) fetchSponsorBlockSegments() // show comments if related streams disabled if (!relatedStreamsEnabled) toggleComments() // prepare for autoplay @@ -798,6 +784,24 @@ class PlayerFragment : Fragment() { run() } + /** + * fetch the segments for SponsorBlock + */ + private fun fetchSponsorBlockSegments() { + CoroutineScope(Dispatchers.IO).launch { + kotlin.runCatching { + val categories = PlayerHelper.getSponsorBlockCategories() + if (categories.size > 0) { + segmentData = + RetrofitInstance.api.getSegments( + videoId!!, + ObjectMapper().writeValueAsString(categories) + ) + } + } + } + } + private fun refreshLiveStatus() { // switch back to normal speed when on the end of live stream if (exoPlayer.duration - exoPlayer.currentPosition < 7000) { @@ -899,56 +903,6 @@ class PlayerFragment : Fragment() { } } - private fun fetchSponsorBlockSegments() { - fun run() { - lifecycleScope.launch(Dispatchers.IO) { - if (sponsorBlockPrefs.sponsorBlockEnabled) { - val categories: ArrayList = arrayListOf() - if (sponsorBlockPrefs.introEnabled) { - categories.add("intro") - } - if (sponsorBlockPrefs.selfPromoEnabled) { - categories.add("selfpromo") - } - if (sponsorBlockPrefs.interactionEnabled) { - categories.add("interaction") - } - if (sponsorBlockPrefs.sponsorsEnabled) { - categories.add("sponsor") - } - if (sponsorBlockPrefs.outroEnabled) { - categories.add("outro") - } - if (sponsorBlockPrefs.fillerEnabled) { - categories.add("filler") - } - if (sponsorBlockPrefs.musicOffTopicEnabled) { - categories.add("music_offtopic") - } - if (sponsorBlockPrefs.previewEnabled) { - categories.add("preview") - } - if (categories.size > 0) { - segmentData = try { - RetrofitInstance.api.getSegments( - videoId!!, - "[\"" + TextUtils.join("\",\"", categories) + "\"]" - ) - } catch (e: IOException) { - println(e) - Log.e(TAG, "IOException, you might not have internet connection") - return@launch - } catch (e: HttpException) { - Log.e(TAG, "HttpException, unexpected response") - return@launch - } - } - } - } - } - run() - } - private fun prepareExoPlayerView() { exoPlayerView.apply { setShowSubtitleButton(true) @@ -1014,8 +968,8 @@ class PlayerFragment : Fragment() { // Listener for play and pause icon change exoPlayer.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { - if (isPlaying && sponsorBlockPrefs.sponsorBlockEnabled) { - exoPlayerView.postDelayed( + if (isPlaying && sponsorBlockEnabled) { + Handler(Looper.getMainLooper()).postDelayed( this@PlayerFragment::checkForSegments, 100 ) diff --git a/app/src/main/java/com/github/libretube/fragments/PlaylistFragment.kt b/app/src/main/java/com/github/libretube/fragments/PlaylistFragment.kt index 1ea1cf89e..ea673d5b5 100644 --- a/app/src/main/java/com/github/libretube/fragments/PlaylistFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/PlaylistFragment.kt @@ -7,7 +7,9 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.github.libretube.R import com.github.libretube.adapters.PlaylistAdapter import com.github.libretube.databinding.FragmentPlaylistBinding @@ -112,6 +114,35 @@ class PlaylistFragment : Fragment() { // scroll view is not at bottom } } + + /** + * listener for swiping to the left or right + */ + if (isOwner) { + val itemTouchCallback = object : ItemTouchHelper.SimpleCallback( + 0, + ItemTouchHelper.RIGHT + ) { + override fun onMove( + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + target: RecyclerView.ViewHolder + ): Boolean { + return false + } + + override fun onSwiped( + viewHolder: RecyclerView.ViewHolder, + direction: Int + ) { + val position = viewHolder.absoluteAdapterPosition + playlistAdapter!!.removeFromPlaylist(position) + } + } + + val itemTouchHelper = ItemTouchHelper(itemTouchCallback) + itemTouchHelper.attachToRecyclerView(binding.playlistRecView) + } } } } diff --git a/app/src/main/java/com/github/libretube/obj/SponsorBlockPrefs.kt b/app/src/main/java/com/github/libretube/obj/SponsorBlockPrefs.kt deleted file mode 100644 index 73118d5ca..000000000 --- a/app/src/main/java/com/github/libretube/obj/SponsorBlockPrefs.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.libretube.obj - -class SponsorBlockPrefs( - var sponsorBlockEnabled: Boolean = false, - var sponsorNotificationsEnabled: Boolean = false, - var sponsorsEnabled: Boolean = false, - var selfPromoEnabled: Boolean = false, - var interactionEnabled: Boolean = false, - var introEnabled: Boolean = false, - var outroEnabled: Boolean = false, - var fillerEnabled: Boolean = false, - var musicOffTopicEnabled: Boolean = false, - var previewEnabled: Boolean = false -) diff --git a/app/src/main/java/com/github/libretube/services/BackgroundMode.kt b/app/src/main/java/com/github/libretube/services/BackgroundMode.kt index a2361f8a5..5c8c4c538 100644 --- a/app/src/main/java/com/github/libretube/services/BackgroundMode.kt +++ b/app/src/main/java/com/github/libretube/services/BackgroundMode.kt @@ -7,15 +7,21 @@ import android.app.Service import android.content.Context import android.content.Intent import android.os.Build +import android.os.Handler import android.os.IBinder +import android.os.Looper import android.support.v4.media.session.MediaSessionCompat +import com.fasterxml.jackson.databind.ObjectMapper import com.github.libretube.BACKGROUND_CHANNEL_ID import com.github.libretube.PLAYER_NOTIFICATION_ID import com.github.libretube.R +import com.github.libretube.obj.Segment +import com.github.libretube.obj.Segments import com.github.libretube.obj.Streams import com.github.libretube.preferences.PreferenceHelper import com.github.libretube.preferences.PreferenceKeys import com.github.libretube.util.DescriptionAdapter +import com.github.libretube.util.PlayerHelper import com.github.libretube.util.RetrofitInstance import com.github.libretube.util.toID import com.google.android.exoplayer2.C @@ -25,6 +31,8 @@ import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.ui.PlayerNotificationManager +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -32,6 +40,11 @@ import kotlinx.coroutines.runBlocking * Loads the selected videos audio in background mode with a notification area. */ class BackgroundMode : Service() { + /** + * VideoId of the video + */ + private lateinit var videoId: String + /** * The response that gets when called the Api. */ @@ -63,8 +76,16 @@ class BackgroundMode : Service() { */ private lateinit var audioAttributes: AudioAttributes + /** + * SponsorBlock Segment data + */ + private var segmentData: Segments? = null + override fun onCreate() { super.onCreate() + /** + * setting the required notification for running as a foreground service + */ if (Build.VERSION.SDK_INT >= 26) { val channelId = BACKGROUND_CHANNEL_ID val channel = NotificationChannel( @@ -89,7 +110,7 @@ class BackgroundMode : Service() { destroyPlayer() // get the intent arguments - val videoId = intent?.getStringExtra("videoId")!! + videoId = intent?.getStringExtra("videoId")!! val position = intent.getLongExtra("position", 0L) // play the audio in the background @@ -121,6 +142,8 @@ class BackgroundMode : Service() { // seek to the previous position if available if (seekToPosition != 0L) player?.seekTo(seekToPosition) + + fetchSponsorBlockSegments() } } @@ -175,6 +198,8 @@ class BackgroundMode : Service() { destroyPlayer() // play new video on background + this.videoId = videoId + this.segmentData = null playAudio(videoId) } } @@ -222,6 +247,43 @@ class BackgroundMode : Service() { mediaSessionConnector.setPlayer(player) } + /** + * fetch the segments for SponsorBlock + */ + private fun fetchSponsorBlockSegments() { + CoroutineScope(Dispatchers.IO).launch { + kotlin.runCatching { + val categories = PlayerHelper.getSponsorBlockCategories() + if (categories.size > 0) { + segmentData = + RetrofitInstance.api.getSegments( + videoId, + ObjectMapper().writeValueAsString(categories) + ) + checkForSegments() + } + } + } + } + + /** + * check for SponsorBlock segments + */ + private fun checkForSegments() { + Handler(Looper.getMainLooper()).postDelayed(this::checkForSegments, 100) + + if (segmentData == null || segmentData!!.segments.isEmpty()) return + + segmentData!!.segments.forEach { segment: Segment -> + val segmentStart = (segment.segment!![0] * 1000f).toLong() + val segmentEnd = (segment.segment[1] * 1000f).toLong() + val currentPosition = player?.currentPosition + if (currentPosition in segmentStart until segmentEnd) { + player?.seekTo(segmentEnd) + } + } + } + private fun destroyPlayer() { // clear old player and its notification playerNotification = null diff --git a/app/src/main/java/com/github/libretube/util/PlayerHelper.kt b/app/src/main/java/com/github/libretube/util/PlayerHelper.kt index ec869d974..81fcccae5 100644 --- a/app/src/main/java/com/github/libretube/util/PlayerHelper.kt +++ b/app/src/main/java/com/github/libretube/util/PlayerHelper.kt @@ -68,4 +68,68 @@ object PlayerHelper { CaptionStyleCompat.createFromCaptionStyle(captioningManager.userStyle) } } + + /** + * get the categories for sponsorBlock + */ + fun getSponsorBlockCategories(): ArrayList { + val categories: ArrayList = arrayListOf() + if (PreferenceHelper.getBoolean( + "intro_category_key", + false + ) + ) { + categories.add("intro") + } + if (PreferenceHelper.getBoolean( + "selfpromo_category_key", + false + ) + ) { + categories.add("selfpromo") + } + if (PreferenceHelper.getBoolean( + "interaction_category_key", + false + ) + ) { + categories.add("interaction") + } + if (PreferenceHelper.getBoolean( + "sponsors_category_key", + true + ) + ) { + categories.add("sponsor") + } + if (PreferenceHelper.getBoolean( + "outro_category_key", + false + ) + ) { + categories.add("outro") + } + if (PreferenceHelper.getBoolean( + "filler_category_key", + false + ) + ) { + categories.add("filler") + } + if (PreferenceHelper.getBoolean( + "music_offtopic_category_key", + false + ) + ) { + categories.add("music_offtopic") + } + if (PreferenceHelper.getBoolean( + "preview_category_key", + false + ) + ) { + categories.add("preview") + } + return categories + } }