From b3d83491b17df7ae5c04c97611b91230bb835530 Mon Sep 17 00:00:00 2001 From: Premium Tools All <123288695+PremiumToolsAll@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:34:05 +0800 Subject: [PATCH 1/2] SparkTube 1.4.2 seek-bounds update: DeliberateSeekTimeBar portrait seek gate (14dp horizontal-dominance), surface swipe seek, card Download action --- AUDIO_DOWNLOADS_BN.md | 39 + BUILD_NOTES_BN.md | 36 + LANDSCAPE_BRIGHTNESS_PIP_BN.md | 35 + LANDSCAPE_GESTURES_PIP_BN.md | 42 + NOTIFICATION_AUDIO_FIX_BN.md | 34 + PIP_FIX_BN.md | 35 + PLAYER_CORRECTION_BN.md | 54 + PORTRAIT_SEEK_BN.md | 28 + RENDER_CONTROLS_BN.md | 37 + RESPONSIVE_CONTROLS_BN.md | 41 + SIZE_OPTIMIZATION_BN.md | 58 + SLEEP_TIMER_BN.md | 35 + VIDEO_COLLAPSE_BN.md | 29 + app/build.gradle.kts | 19 +- app/libs/test/hamcrest-core-1.3.jar | Bin 0 -> 45024 bytes app/libs/test/junit-4.13.2.jar | Bin 0 -> 384581 bytes .../app/data/ProgressiveFeedLoader.kt | 39 + .../com/sparktube/app/data/YtRepository.kt | 28 +- .../sparktube/app/download/DownloadCenter.kt | 161 +- .../sparktube/app/download/DownloadMetrics.kt | 73 + .../sparktube/app/download/DownloadSharing.kt | 50 + .../app/playback/NotificationTransport.kt | 37 + .../app/playback/PendingPlaybackRequest.kt | 32 + .../sparktube/app/playback/PipSessionState.kt | 28 + .../sparktube/app/playback/PlaybackCenter.kt | 510 ++- .../sparktube/app/playback/PlaybackService.kt | 84 +- .../app/playback/PlayerListenerBridge.java | 164 + .../app/playback/QueueNavigationPolicy.kt | 8 + .../sparktube/app/playback/SleepTimerState.kt | 57 + .../app/playback/VideoFramePolicy.kt | 16 + .../java/com/sparktube/app/ui/MainActivity.kt | 20 + .../app/ui/common/DownloadAdapter.kt | 119 +- .../app/ui/common/DownloadMetricFormat.kt | 30 + .../sparktube/app/ui/common/VideoAdapter.kt | 46 +- .../app/ui/common/VideoDownloadAction.kt | 61 + .../sparktube/app/ui/home/HomeFeedCache.kt | 104 + .../com/sparktube/app/ui/home/HomeFragment.kt | 92 +- .../app/ui/library/LibraryFragment.kt | 99 +- .../app/ui/music/AudioDownloadLibrary.kt | 20 + .../app/ui/music/MusicListActivity.kt | 141 +- .../app/ui/music/NowPlayingActivity.kt | 30 + .../sparktube/app/ui/music/SleepTimerSheet.kt | 125 + .../app/ui/player/DeliberateSeekTimeBar.kt | 181 + .../sparktube/app/ui/player/PlayerActivity.kt | 406 +- .../app/ui/player/PlayerGestureMath.kt | 48 + .../app/ui/player/SeekBarDragGate.kt | 33 + .../app/ui/player/SeekBarTouchGeometry.kt | 29 + app/src/main/res/drawable/bg_mode_switch.xml | 5 + .../res/drawable/bg_mode_switch_active.xml | 5 + .../res/drawable/bg_mode_switch_hit_area.xml | 10 + .../main/res/drawable/bg_transport_circle.xml | 11 + .../main/res/drawable/ic_download_more.xml | 7 + app/src/main/res/drawable/ic_sleep_timer.xml | 6 + .../main/res/layout/activity_now_playing.xml | 69 +- app/src/main/res/layout/activity_player.xml | 129 +- app/src/main/res/layout/fragment_home.xml | 8 - app/src/main/res/layout/item_download.xml | 43 +- app/src/main/res/layout/item_video.xml | 18 +- .../res/layout/player_queue_controller.xml | 127 + .../main/res/values-port/dimens_player.xml | 6 + app/src/main/res/values/strings.xml | 4 + .../res/values/strings_download_controls.xml | 22 + .../res/values/strings_player_controls.xml | 11 + .../main/res/values/strings_sleep_timer.xml | 12 + app/src/main/res/xml/file_paths.xml | 3 + .../app/data/ProgressiveFeedLoaderTest.kt | 80 + .../app/download/DownloadMetricsTest.kt | 66 + .../app/playback/NotificationTransportTest.kt | 101 + .../playback/PendingPlaybackRequestTest.kt | 74 + .../app/playback/PipSessionStateTest.kt | 86 + .../app/playback/PlayerListenerBridgeTest.kt | 59 + .../app/playback/QueueNavigationPolicyTest.kt | 51 + .../app/playback/SleepTimerStateTest.kt | 93 + .../app/playback/VideoFramePolicyTest.kt | 27 + .../app/ui/common/DownloadMetricFormatTest.kt | 40 + .../app/ui/home/HomeFeedCachePolicyTest.kt | 36 + .../app/ui/music/AudioDownloadLibraryTest.kt | 60 + .../app/ui/player/PlayerGestureMathTest.kt | 53 + .../app/ui/player/PortraitSeekPolicyTest.kt | 39 + .../app/ui/player/SeekBarDragGateTest.kt | 86 + .../app/ui/player/SeekBarTouchGeometryTest.kt | 60 + gradlew.bat | 184 +- tools/apk-size-audit.py | 102 + tools/package-audio-downloads.py | 129 + tools/package-audio-timer-position.py | 93 + tools/package-clean-player-overlay.py | 98 + tools/package-compact-controls-bottom-seek.py | 106 + tools/package-compact-toggle.py | 113 + tools/package-first-tap.py | 127 + tools/package-gestures-download.py | 153 + tools/package-landscape-brightness-pip.py | 88 + tools/package-landscape-gestures-pip.py | 91 + tools/package-pip-fix.py | 134 + tools/package-portrait-seek.py | 121 + tools/package-render-controls.py | 122 + tools/package-responsive-controls.py | 141 + tools/package-seek-bounds.py | 170 + tools/package-sleep-timer.py | 93 + tools/package-video-collapse.py | 96 + tools/package-video-overflow.py | 108 + tools/verify-audio-downloads.py | 61 + tools/verify-audio-timer-position.py | 51 + tools/verify-clean-player-overlay.py | 94 + tools/verify-compact-controls-bottom-seek.py | 78 + tools/verify-current-update.py | 30 + tools/verify-download-progress-navigation.py | 42 + tools/verify-fast-home-feed.py | 38 + tools/verify-first-tap.py | 69 + tools/verify-horizontal-player-gestures.py | 38 + tools/verify-landscape-brightness-pip.py | 78 + tools/verify-landscape-gestures-pip.py | 45 + tools/verify-notification-response.py | 86 + tools/verify-pip-fix.py | 60 + tools/verify-player-correction.py | 101 + tools/verify-sleep-timer.py | 66 + tools/verify-video-collapse.py | 86 + ...ube.app.data.ProgressiveFeedLoaderTest.xml | 12 + ...ktube.app.download.DownloadMetricsTest.xml | 14 + ...app.playback.NotificationTransportTest.xml | 17 + ...pp.playback.PendingPlaybackRequestTest.xml | 14 + ...ktube.app.playback.PipSessionStateTest.xml | 12 + ....app.playback.PlayerListenerBridgeTest.xml | 9 + ...app.playback.QueueNavigationPolicyTest.xml | 14 + ...ktube.app.playback.SleepTimerStateTest.xml | 13 + ...tube.app.playback.VideoFramePolicyTest.xml | 10 + ...app.ui.common.DownloadMetricFormatTest.xml | 14 + ...be.app.ui.home.HomeFeedCachePolicyTest.xml | 9 + ....app.ui.music.AudioDownloadLibraryTest.xml | 14 + ...be.app.ui.player.PlayerGestureMathTest.xml | 16 + ...e.app.ui.player.PortraitSeekPolicyTest.xml | 14 + ...tube.app.ui.player.SeekBarDragGateTest.xml | 21 + ...app.ui.player.SeekBarTouchGeometryTest.xml | 26 + verification/seek-bounds/apk-badging.txt | 117 + .../build-offline-lint-unavailable.log | 77 + verification/seek-bounds/build.log | 3919 +++++++++++++++++ verification/seek-bounds/package.log | 0 verification/seek-bounds/signature-after.txt | 17 + verification/seek-bounds/signature-before.txt | 17 + .../seek-bounds/verification-summary.json | 29 + verification/seek-bounds/zipalign.txt | 1322 ++++++ 140 files changed, 13466 insertions(+), 453 deletions(-) create mode 100644 AUDIO_DOWNLOADS_BN.md create mode 100644 BUILD_NOTES_BN.md create mode 100644 LANDSCAPE_BRIGHTNESS_PIP_BN.md create mode 100644 LANDSCAPE_GESTURES_PIP_BN.md create mode 100644 NOTIFICATION_AUDIO_FIX_BN.md create mode 100644 PIP_FIX_BN.md create mode 100644 PLAYER_CORRECTION_BN.md create mode 100644 PORTRAIT_SEEK_BN.md create mode 100644 RENDER_CONTROLS_BN.md create mode 100644 RESPONSIVE_CONTROLS_BN.md create mode 100644 SIZE_OPTIMIZATION_BN.md create mode 100644 SLEEP_TIMER_BN.md create mode 100644 VIDEO_COLLAPSE_BN.md create mode 100644 app/libs/test/hamcrest-core-1.3.jar create mode 100644 app/libs/test/junit-4.13.2.jar create mode 100644 app/src/main/java/com/sparktube/app/data/ProgressiveFeedLoader.kt create mode 100644 app/src/main/java/com/sparktube/app/download/DownloadMetrics.kt create mode 100644 app/src/main/java/com/sparktube/app/download/DownloadSharing.kt create mode 100644 app/src/main/java/com/sparktube/app/playback/NotificationTransport.kt create mode 100644 app/src/main/java/com/sparktube/app/playback/PendingPlaybackRequest.kt create mode 100644 app/src/main/java/com/sparktube/app/playback/PipSessionState.kt create mode 100644 app/src/main/java/com/sparktube/app/playback/PlayerListenerBridge.java create mode 100644 app/src/main/java/com/sparktube/app/playback/QueueNavigationPolicy.kt create mode 100644 app/src/main/java/com/sparktube/app/playback/SleepTimerState.kt create mode 100644 app/src/main/java/com/sparktube/app/playback/VideoFramePolicy.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/common/DownloadMetricFormat.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/common/VideoDownloadAction.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/home/HomeFeedCache.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/music/AudioDownloadLibrary.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/music/SleepTimerSheet.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/player/DeliberateSeekTimeBar.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/player/PlayerGestureMath.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/player/SeekBarDragGate.kt create mode 100644 app/src/main/java/com/sparktube/app/ui/player/SeekBarTouchGeometry.kt create mode 100644 app/src/main/res/drawable/bg_mode_switch.xml create mode 100644 app/src/main/res/drawable/bg_mode_switch_active.xml create mode 100644 app/src/main/res/drawable/bg_mode_switch_hit_area.xml create mode 100644 app/src/main/res/drawable/bg_transport_circle.xml create mode 100644 app/src/main/res/drawable/ic_download_more.xml create mode 100644 app/src/main/res/drawable/ic_sleep_timer.xml create mode 100644 app/src/main/res/layout/player_queue_controller.xml create mode 100644 app/src/main/res/values-port/dimens_player.xml create mode 100644 app/src/main/res/values/strings_download_controls.xml create mode 100644 app/src/main/res/values/strings_player_controls.xml create mode 100644 app/src/main/res/values/strings_sleep_timer.xml create mode 100644 app/src/test/java/com/sparktube/app/data/ProgressiveFeedLoaderTest.kt create mode 100644 app/src/test/java/com/sparktube/app/download/DownloadMetricsTest.kt create mode 100644 app/src/test/java/com/sparktube/app/playback/NotificationTransportTest.kt create mode 100644 app/src/test/java/com/sparktube/app/playback/PendingPlaybackRequestTest.kt create mode 100644 app/src/test/java/com/sparktube/app/playback/PipSessionStateTest.kt create mode 100644 app/src/test/java/com/sparktube/app/playback/PlayerListenerBridgeTest.kt create mode 100644 app/src/test/java/com/sparktube/app/playback/QueueNavigationPolicyTest.kt create mode 100644 app/src/test/java/com/sparktube/app/playback/SleepTimerStateTest.kt create mode 100644 app/src/test/java/com/sparktube/app/playback/VideoFramePolicyTest.kt create mode 100644 app/src/test/java/com/sparktube/app/ui/common/DownloadMetricFormatTest.kt create mode 100644 app/src/test/java/com/sparktube/app/ui/home/HomeFeedCachePolicyTest.kt create mode 100644 app/src/test/java/com/sparktube/app/ui/music/AudioDownloadLibraryTest.kt create mode 100644 app/src/test/java/com/sparktube/app/ui/player/PlayerGestureMathTest.kt create mode 100644 app/src/test/java/com/sparktube/app/ui/player/PortraitSeekPolicyTest.kt create mode 100644 app/src/test/java/com/sparktube/app/ui/player/SeekBarDragGateTest.kt create mode 100644 app/src/test/java/com/sparktube/app/ui/player/SeekBarTouchGeometryTest.kt create mode 100644 tools/apk-size-audit.py create mode 100644 tools/package-audio-downloads.py create mode 100644 tools/package-audio-timer-position.py create mode 100644 tools/package-clean-player-overlay.py create mode 100644 tools/package-compact-controls-bottom-seek.py create mode 100644 tools/package-compact-toggle.py create mode 100644 tools/package-first-tap.py create mode 100644 tools/package-gestures-download.py create mode 100644 tools/package-landscape-brightness-pip.py create mode 100644 tools/package-landscape-gestures-pip.py create mode 100644 tools/package-pip-fix.py create mode 100644 tools/package-portrait-seek.py create mode 100644 tools/package-render-controls.py create mode 100644 tools/package-responsive-controls.py create mode 100644 tools/package-seek-bounds.py create mode 100644 tools/package-sleep-timer.py create mode 100644 tools/package-video-collapse.py create mode 100644 tools/package-video-overflow.py create mode 100644 tools/verify-audio-downloads.py create mode 100644 tools/verify-audio-timer-position.py create mode 100644 tools/verify-clean-player-overlay.py create mode 100644 tools/verify-compact-controls-bottom-seek.py create mode 100644 tools/verify-current-update.py create mode 100644 tools/verify-download-progress-navigation.py create mode 100644 tools/verify-fast-home-feed.py create mode 100644 tools/verify-first-tap.py create mode 100644 tools/verify-horizontal-player-gestures.py create mode 100644 tools/verify-landscape-brightness-pip.py create mode 100644 tools/verify-landscape-gestures-pip.py create mode 100644 tools/verify-notification-response.py create mode 100644 tools/verify-pip-fix.py create mode 100644 tools/verify-player-correction.py create mode 100644 tools/verify-sleep-timer.py create mode 100644 tools/verify-video-collapse.py create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.data.ProgressiveFeedLoaderTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.download.DownloadMetricsTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.playback.NotificationTransportTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.playback.PendingPlaybackRequestTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.playback.PipSessionStateTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.playback.PlayerListenerBridgeTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.playback.QueueNavigationPolicyTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.playback.SleepTimerStateTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.playback.VideoFramePolicyTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.ui.common.DownloadMetricFormatTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.ui.home.HomeFeedCachePolicyTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.ui.music.AudioDownloadLibraryTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.ui.player.PlayerGestureMathTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.ui.player.PortraitSeekPolicyTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.ui.player.SeekBarDragGateTest.xml create mode 100644 verification/seek-bounds/TEST-com.sparktube.app.ui.player.SeekBarTouchGeometryTest.xml create mode 100644 verification/seek-bounds/apk-badging.txt create mode 100644 verification/seek-bounds/build-offline-lint-unavailable.log create mode 100644 verification/seek-bounds/build.log create mode 100644 verification/seek-bounds/package.log create mode 100644 verification/seek-bounds/signature-after.txt create mode 100644 verification/seek-bounds/signature-before.txt create mode 100644 verification/seek-bounds/verification-summary.json create mode 100644 verification/seek-bounds/zipalign.txt diff --git a/AUDIO_DOWNLOADS_BN.md b/AUDIO_DOWNLOADS_BN.md new file mode 100644 index 0000000..21de9db --- /dev/null +++ b/AUDIO_DOWNLOADS_BN.md @@ -0,0 +1,39 @@ +# SparkTube 1.4.1 — অডিও ডাউনলোড টেস্ট বিল্ড + +## নতুন কী + +Music → Downloads পেজে এখন শুধু সম্পন্ন গান নয়, চলমান, অপেক্ষমাণ, paused ও ব্যর্থ অডিও ডাউনলোডও দেখা যায়। ভিডিওর একই download adapter/engine দিয়ে pause/resume, progress bar, শতাংশ (মোট সাইজ জানা থাকলে), downloaded/total MB, MB/s এবং বাকি সময় দেখায়। অজানা মোট সাইজ বা সময়কে অনুমান করে দেখায় না। + +গানের সারিতে বর্গাকার album art, শিরোনাম ও artist আছে। উপরে গানের সংখ্যা ও সবুজ Play all রাখা হয়েছে। গান গোনা ও Play all-এর কিউতে শুধু সম্পূর্ণ এবং ডিস্কে থাকা অডিও যায়; আংশিক ফাইল বাজানো হয় না। সম্পন্ন গানের তিন-ডট মেনুতে Open, Share, Delete আছে; Delete-এ confirmation দেখায়। চলমান/ব্যর্থ সারি long-press করে মুছতে পারেন। পেজ খোলা থাকলে প্রতি সেকেন্ডে অগ্রগতি আপডেট হয়; অন্য পেজে গেলে polling বন্ধ হয়। + +## যা অপরিবর্তিত + +`com.sparktube.app`, versionName **1.4.1**, versionCode **10** এবং আগের টেস্ট APK-এর signing certificate একই। ভিডিও download engine, Library downloads, player ও compact Audio/Video toggle-এর সোর্স অপরিবর্তিত। DownloadAdapter-এ audioStyle opt-in; পুরোনো default video view আগের পথেই চলে। নতুন dependency, native library বা বড় image asset যোগ হয়নি। + +APK: **10,303,832 bytes** (10.30 MB); আগের compact-toggle APK থেকে পরিবর্তন **+2,432 bytes**। এটি debug-signed test build, store release নয়। + +## যাচাই ও সীমা + +বিল্ড সফল; **46 JVM tests** (অডিও লাইব্রেরির 8 নতুন test-সহ) এবং **15 source/XML structure checks** পাস। APK package/version, signature এবং ZIP alignment যাচাই করা হয়েছে। আগের editable ZIP-এর সঙ্গে মিলিয়ে পুরোনো app source-এ কেবল MusicListActivity ও DownloadAdapter বদলানোর বিষয়টি পরীক্ষা হয়েছে। এটি source-level regression evidence, runtime-এর নিশ্চয়তা নয়। + +কোনো Android ফোন বা emulator সংযুক্ত নেই। তাই আসল ফোনে install, screen rendering, download, pause/resume, Play all, playback, share/delete-এর end-to-end পরীক্ষা হয়নি। Android lint offline cache-এ প্রয়োজনীয় `lint-gradle:31.6.1` না থাকায় lintVital ধাপ বাদ দেওয়া হয়েছে; full lint সম্পন্ন হয়নি। আগের deprecation warnings আছে। + +আগের সীমাবদ্ধতা একই: pause/resume কেবল চলমান app process-এর মধ্যে; process kill বা restart-এর পর অসম্পূর্ণ download আবার শুরু করতে হবে। Range সমর্থন না করা server pause-এর সময় সংযোগ ছিন্ন করলে download ব্যর্থ হতে পারে। ব্যর্থ item long-press করে মুছে পুনরায় download করতে হবে; নতুন retry/restart button যোগ করা হয়নি। + +## সোর্স থেকে বিল্ড + +JDK 21 ও Android SDK ব্যবহার করুন; নিজের SDK path দিয়ে `local.properties` তৈরি করুন। `app/build.gradle.kts`-এ version বদলাবেন না। + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest +``` + +এই পরিবেশে offline build command: + +```sh +JAVA_HOME=/workspace/.jdk/temurin-21 ANDROID_HOME=/workspace/android-sdk ./gradlew --offline :app:assembleCompactTest :app:testCompactTestUnitTest -x :app:lintVitalAnalyzeCompactTest -x :app:lintVitalReportCompactTest -x :app:lintVitalCompactTest +python3 tools/verify-player-correction.py +python3 tools/verify-audio-downloads.py +``` + +`verification/audio-downloads/`-এ বর্তমান বিল্ড ও টেস্ট ফল রয়েছে। আগের ZIP-নির্ভর byte comparison script-এর জন্য আগের compact-toggle source ZIP parent folder-এ রাখতে হয়; APK build বা JVM tests-এর জন্য তা লাগে না। সোর্স ZIP-এ local SDK path, build cache, APK ও signing key নেই। চূড়ান্ত APK ও source ZIP-এর SHA-256 আলাদা manifest-এ আছে। diff --git a/BUILD_NOTES_BN.md b/BUILD_NOTES_BN.md new file mode 100644 index 0000000..b0ba924 --- /dev/null +++ b/BUILD_NOTES_BN.md @@ -0,0 +1,36 @@ +# SparkTube 1.4.1 — টেস্ট বিল্ড + +> এই ZIP-এর সর্বশেষ ছোট APK, লকস্ক্রিন Next ও Audio/Video পিলের পরিবর্তন এবং বিল্ড নির্দেশনা `SIZE_OPTIMIZATION_BN.md`-এ আছে। নিচের নোটগুলো আগের বড় debug টেস্ট বিল্ডের ইতিহাস; `assembleDebug` এখনও সেই ডিবাগ ধরনের বিল্ড তৈরি করে। + +মূল Kotlin Android প্রজেক্টেই পরিবর্তন করা হয়েছে। প্যাকেজ `com.sparktube.app`, `versionName = 1.4.1` ও `versionCode = 10` অপরিবর্তিত। এটি debug-signed টেস্ট APK, প্রকাশনার release APK নয়। আগের original-debug APK-এর সঙ্গে signing certificate একই। + +## পরিবর্তন + +- সার্চ রাখা হয়েছে; পাশের crash-test/bug বাটন সরানো হয়েছে। +- Library-র তালিকায় ডানে/বামে সোয়াইপ করলে পাশের ক্যাটাগরি নির্বাচিত হয়। Downloads-এ অডিও ও ভিডিও উভয় ট্রান্সফার দেখা যায়। +- ডাউনলোডে pause/resume, শতাংশ, প্রতি সেকেন্ডে আপডেট হওয়া Mbps এবং সম্পন্ন ফাইল অন্য অ্যাপে শেয়ার করার কন্ট্রোল আছে। রেঞ্জ ডাউনলোডে পুনঃচেষ্টার সময় লেখা বাইটের পর থেকে ডাউনলোড হয়। +- ভিডিওর প্রকাশ্য ১০-সেকেন্ড rewind/forward বাটন লুকানো হয়েছে। ভিডিওর ফাঁকা ডান/বাম অংশে ডাবল-ট্যাপ করলে ১০ সেকেন্ড সামনে/পিছনে যায়। Play/pause, previous/next, seekbar ও settings রাখা হয়েছে; মাঝের play/pause বাটন গোলাকার। +- ভিডিওর উপরের কোণায় Audio এবং অডিও পেজে Video সুইচ আছে। অনলাইন অডিও থেকে ভিডিওতে গেলে অবস্থান ও অবশিষ্ট কিউ সংরক্ষণের ব্যবস্থা আছে। +- ভিডিওর ডান দিকে উল্লম্ব সোয়াইপে media volume, বামে বর্তমান window-র brightness বদলায়। শতাংশের feedback দেখায়। মাঝখানে নিচে সোয়াইপে আগের মতো minimize হয়। বাস্তব বাটন ও seekbar জেসচার থেকে বাদ রাখা হয়েছে। + +## যাচাই + +`assembleDebug` ও `testDebugUnitTest` সফল। জেসচার গণনার ৭টি JVM টেস্ট পাস: দুই দিকের ১০-সেকেন্ড সিক, শুরু/শেষের সীমা, অজানা duration, overflow, volume ও brightness সীমা। এটি touch dispatch বা আসল playback/download-এর end-to-end পরীক্ষা নয়। সব ১৩০টি XML parse হয়েছে। APK-এর স্বাক্ষর এবং ভেতরের package/version যাচাই করা হয়েছে। + +কোনো Android ফোন বা emulator সংযুক্ত ছিল না; ফোনে install, playback, pause/resume, share এবং gesture-এর ব্যবহারিক পরীক্ষা হয়নি। Android lint চালানোর চেষ্টা হয়েছে, কিন্তু প্রয়োজনীয় `lint-gradle:31.6.1` অফলাইন ক্যাশে না থাকায় সম্পন্ন হয়নি। বিদ্যমান Kotlin/D8 metadata warning আছে; তা সত্ত্বেও APK বিল্ড সফল। + +## সীমাবদ্ধতা + +Pause/resume এই অ্যাপের চলমান প্রসেসের মধ্যে কাজ করে। প্রসেস মেরে ফেলা বা ফোন restart-এর পর আংশিক ডাউনলোড পুনরারম্ভ হয় না; নতুন করে ডাউনলোড প্রয়োজন। Pause চাপার সময় চলমান একটি blocking read শেষ হতে পারে। রেঞ্জ সমর্থন না করা সার্ভারের সংযোগ pause-এর পরে বিচ্ছিন্ন হলে ট্রান্সফার ব্যর্থ হতে পারে। + +AV pair ডাউনলোড আগের মতো পৃথক video ও audio track; শেয়ারের সময় দুটি ফাইল যায়, একত্রিত MP4 নয়। শুধু স্থানীয় অডিও ফাইলে ভিডিও ট্র্যাক না থাকলে Video সুইচ সতর্কবার্তা দেখায়। + +## আবার বিল্ড + +Android Studio-তে ফোল্ডার খুলুন। Android SDK 35, build-tools 34.0.0 ও উপযুক্ত JDK (এই বিল্ডে JDK 21) ব্যবহার করুন। নিজের SDK path দিয়ে `local.properties` তৈরি করুন অথবা `ANDROID_HOME` সেট করুন। + +```sh +./gradlew :app:assembleDebug :app:testDebugUnitTest +``` + +প্রথমবার Gradle/plugin/Android লাইব্রেরি ডাউনলোডের জন্য ইন্টারনেট প্রয়োজন। পরবর্তীতে ক্যাশ সম্পূর্ণ থাকলে `--offline` ব্যবহার করা যায়। টেস্টের JUnit ও Hamcrest jar `app/libs/test/`-এ রাখা আছে; এগুলো production APK dependency নয়। `verification/` ফোল্ডারে বিল্ড লগ ও টেস্ট ফল দেওয়া হয়েছে। স্থানীয় SDK path, build cache ও signing key সোর্স ZIP-এ নেই। diff --git a/LANDSCAPE_BRIGHTNESS_PIP_BN.md b/LANDSCAPE_BRIGHTNESS_PIP_BN.md new file mode 100644 index 0000000..47fd379 --- /dev/null +++ b/LANDSCAPE_BRIGHTNESS_PIP_BN.md @@ -0,0 +1,35 @@ +# SparkTube 1.4.1 — Landscape brightness ও PiP cleanup + +## পরিবর্তন + +Portrait অবস্থায় বাম পাশের vertical swipe দিয়ে brightness পরিবর্তন হবে না; brightness gesture শুধু landscape-এ কাজ করবে। Landscape থেকে portrait-এ ফিরলে অ্যাপের brightness override বাদ দিয়ে সিস্টেমের brightness ব্যবহার হবে। Volume, double-tap seek, horizontal seek এবং মাঝখানে নিচে swipe করে mini player খোলার আচরণ রাখা হয়েছে। + +নতুন ভিডিও/ভিডিও প্লেলিস্ট/অডিও নির্বাচন করলে আগের PiP session-এর floating Activity বন্ধ করা হয়; শুধু player surface সরিয়ে কালো window ফেলে রাখা হয় না। নতুন watch page খুললেও পুরোনো PiP বন্ধ হয়। বন্ধ হতে থাকা Activity-র দেরিতে আসা callback নতুন player surface নিতে বা নতুন PiP-এর state মুছতে পারবে না। একই PiP-এর ভেতর স্বয়ংক্রিয় queue advance-এ PiP বন্ধ করা হয় না। + +VersionName 1.4.1, versionCode 10 এবং পূর্বের compact test APK-এর signing certificate অপরিবর্তিত। Controller layout, seek bar, downloads, audio, sleep timer এবং অন্য app source অপরিবর্তিত রাখা হয়েছে; সুনির্দিষ্ট file-scope যাচাই `tools/verify-landscape-brightness-pip.py`-তে আছে। + +## বিল্ড ও যাচাই + +JDK 21 এবং Android SDK path দিয়ে `local.properties` তৈরি করে: + +```sh +./gradlew --offline --no-daemon :app:testCompactTestUnitTest :app:assembleCompactTest \ + -x lintVitalAnalyzeCompactTest -x lintVitalReportCompactTest -x lintVitalCompactTest +python3 tools/verify-landscape-brightness-pip.py +python3 tools/package-landscape-brightness-pip.py +``` + +প্যাকেজিং স্ক্রিপ্টের baseline APK/source এবং SDK build-tools path একই workspace-এ থাকা প্রয়োজন। APK Gradle-এর existing debug signing config ব্যবহার করে; আগের APK-র সঙ্গে update compatibility রাখতে সেই একই signing keystore প্রয়োজন। ZIP-এ keystore বা secret দেওয়া হয়নি। নতুন কম্পিউটারে প্রথম build-এ dependency download করার জন্য `--offline` বাদ দিতে হতে পারে। + +`verification/landscape-brightness-pip/`-তে build log, unit test XML, source regression ফল, certificate/zipalign যাচাই এবং summary আছে। Lint চালানো হয়নি; baseline-এর offline lint dependency অনুপস্থিত থাকায় lint-vital task বাদ দেওয়া হয়েছে। + +## ফোনে পরীক্ষা বাকি + +Android ফোন বা emulator সংযুক্ত নেই; PiP window বন্ধ হওয়া ও gesture-এর বাস্তব আচরণ এখানে চালিয়ে পরীক্ষা করা হয়নি। ফোনে পরীক্ষা করুন: + +1. Portrait-এ বাম দিকে উপরে/নিচে swipe করলে brightness বা brightness feedback বদলায় না। +2. Landscape-এ একই swipe brightness বদলায়; portrait-এ ফিরলে সিস্টেম brightness ফিরে আসে। +3. ভিডিও A থেকে PiP খুলে অ্যাপে ফিরে ভিডিও B চালালে A-এর floating window বন্ধ হয়, B মূল player-এ চলে। দ্রুত B/C নির্বাচন করেও পরীক্ষা করুন। +4. নতুন ভিডিও playlist বা audio চালালেও পুরোনো PiP থাকে না। +5. PiP থেকে পূর্ণ পর্দায় ফিরে আসা, আবার PiP খোলা, PiP বন্ধ করা এবং স্বয়ংক্রিয় next-video পরীক্ষা করুন। +6. Volume/seek, mini player, downloads, notification, audio এবং sleep timer আগের মতো কাজ করে কি না দেখুন। diff --git a/LANDSCAPE_GESTURES_PIP_BN.md b/LANDSCAPE_GESTURES_PIP_BN.md new file mode 100644 index 0000000..47bd369 --- /dev/null +++ b/LANDSCAPE_GESTURES_PIP_BN.md @@ -0,0 +1,42 @@ +# SparkTube 1.4.1 — Landscape-only brightness ও volume gesture + +## কী বদলেছে + +Portrait-এ বাম পাশের brightness এবং ডান পাশের sound-volume vertical swipe—দুটিই বন্ধ। Landscape-এ দুটি gesture আগের মতো থাকে। Hardware volume button বা system volume control বন্ধ করা হয়নি। Landscape থেকে portrait-এ ফিরলে আগের মতো system brightness ফিরে আসে। + +Horizontal seek, double-tap seek এবং মাঝখানে নিচে swipe করে minimize করার কোড অপরিবর্তিত। সর্বশেষ PiP cleanup fix, audio, downloads, sleep timer, notification, controls এবং অন্য সব app source অপরিবর্তিত। আগের `landscape-brightness-pip` সোর্সের তুলনায় app source-এ শুধু `PlayerActivity.kt`-এর brightness-only guard দুই side gesture-এর আগের যৌথ জায়গায় সরানো হয়েছে। + +VersionName **1.4.1**, versionCode **10**, compactTest configuration এবং আগের test APK-এর signing certificate বজায় রাখা হয়েছে। `LANDSCAPE_BRIGHTNESS_PIP_BN.md` আগের পরিবর্তনের ঐতিহাসিক নোট; portrait volume সম্পর্কে এই নতুন নোট প্রযোজ্য। + +## পুনরায় বিল্ড + +JDK 21 এবং Android SDK ব্যবহার করুন। নিজের SDK path দিয়ে `local.properties` বানিয়ে চালান: + +```sh +./gradlew --offline --no-daemon :app:testCompactTestUnitTest :app:assembleCompactTest \ + -x lintVitalAnalyzeCompactTest -x lintVitalReportCompactTest -x lintVitalCompactTest +``` + +নতুন কম্পিউটারে dependency download করতে `--offline` বাদ দিতে হতে পারে। APK পাওয়া যাবে `app/build/outputs/apk/compactTest/app-compactTest.apk`-এ। আগের APK-এর update-compatible signing পেতে একই বিদ্যমান debug keystore প্রয়োজন; ZIP-এ keystore বা secret নেই। + +সোর্স রিগ্রেশন ও প্যাকেজিং পুনরায় চালাতে: + +```sh +mkdir -p verification/landscape-gestures-pip +python3 tools/verify-landscape-gestures-pip.py > verification/landscape-gestures-pip/source-checks.log 2>&1 +python3 tools/package-landscape-gestures-pip.py +``` + +এই workspace-কেন্দ্রিক যাচাই স্ক্রিপ্টে আগের source ZIP/APK, পুরোনো compact-controls source ZIP এবং Android SDK build-tools path লাগে। প্যাকেজিং স্ক্রিপ্টে বর্তমান সফল build log থাকতে হবে। অন্য পরিবেশে সংশ্লিষ্ট path সামঞ্জস্য করুন। + +`verification/landscape-gestures-pip/`-এ build log, unit-test XML, source-check log, APK version/signature/zipalign evidence এবং summary রয়েছে। পুরোনো offline lint dependency সীমাবদ্ধতার কারণে lint-vital বাদ দেওয়া হয়েছে; lint pass দাবি করা হচ্ছে না। + +## ফোনে পরীক্ষা বাকি + +কোনো Android device বা emulator সংযুক্ত নেই। তাই APK build, JVM unit test ও static checks করা হলেও বাস্তব gesture/PiP runtime পরীক্ষা হয়নি। ফোনে নিশ্চিত করুন: + +1. Portrait-এ বাম ও ডান side swipe করলে brightness/volume বা সংশ্লিষ্ট feedback বদলায় না। Hardware volume button কাজ করে। +2. Landscape-এ বাম brightness এবং ডান volume swipe কাজ করে; portrait-এ ফিরলে system brightness ফিরে আসে। +3. দুই orientation-এ horizontal seek, double-tap seek, center swipe-down ও player controls কাজ করে। +4. ভিডিও A-এর PiP খোলা রেখে নতুন ভিডিও B, playlist বা audio চালালে পুরোনো floating window বন্ধ হয়। দ্রুত একাধিক ভিডিও নির্বাচন করেও পরীক্ষা করুন। +5. PiP expand/close/reopen ও automatic next-video আগের মতো চলে। Downloads, audio, notification ও sleep timer-ও পরীক্ষা করুন। diff --git a/NOTIFICATION_AUDIO_FIX_BN.md b/NOTIFICATION_AUDIO_FIX_BN.md new file mode 100644 index 0000000..cd8cf5f --- /dev/null +++ b/NOTIFICATION_AUDIO_FIX_BN.md @@ -0,0 +1,34 @@ +# SparkTube 1.4.1 — নোটিফিকেশন ও অডিও ফাইল খোলা + +## এই আপডেটে +নোটিফিকেশন থেকে আসা Play/Pause key-down এখন MediaSession callback-এ সরাসরি session player-কে দেওয়া হয় এবং handled হিসেবে ফেরত যায়। এতে ওই ট্যাপ Media3-এর সাধারণ media-key fallback/command-queue পথে যায় না। Media3 1.3.1-এর bytecode-এ কিছু legacy play/pause key-এর জন্য double-tap timeout এবং দ্বিতীয় ট্যাপকে Next হিসেবে নেওয়ার পথ আছে; নোটিফিকেশনের পৃথক ট্যাপে সেই gesture recognizer প্রযোজ্য নয়। এই পরিবর্তন শুধু notification controller-এর toggle key-এ সীমিত; headset, Bluetooth-এর native transport, Previous/Next ও অন্য commands আগের পথে থাকে। নির্দিষ্ট ফোনে আগের ব্যর্থতার একমাত্র কারণ এটি—এমন runtime প্রমাণ নেই। + +বাফারিংয়ে `isPlaying` নয়, `playWhenReady` অনুযায়ী Play/Pause বদলায়। একই ট্যাপ দ্বিতীয়বার default handler-এ যায় না, held-key repeat উপেক্ষা করা হয়। Idle source-এ prepare এবং ended source-এ restart-এর আচরণ রাখা হয়েছে। আগে করা pending-video pause, queue ও player fixes অপরিবর্তিত। + +Library → Downloads-এ সম্পন্ন AUDIO আইটেমে ট্যাপ করলে তার বিদ্যমান নির্দিষ্ট `file://` ফাইলের এক-আইটেম music queue চালু হয় এবং Now Playing খোলে। অনলাইনে আবার resolve করা হয় না। Audio Downloads তালিকার বিদ্যমান নির্দিষ্ট ফাইল বেছে চালানোর আচরণ রাখা হয়েছে। ফাইল মুছে গেলে missing-file বার্তা আসে; ভিডিও/AV downloads আগের আচরণেই থাকে। + +## যাচাই +বিল্ড সফল। 77 JVM unit tests ও 52 source/XML checks পাস; এগুলো Android runtime test নয়। নতুন transport tests-এ প্রথম ট্যাপ, দ্রুত আলাদা ট্যাপ, buffering, key-up/repeat, headset fallback, command availability এবং idle/ended source আছে। Latest fast-home source-এর সঙ্গে app files তুলনায় কেবল PlaybackService.kt ও LibraryFragment.kt বদলেছে; নতুন handler ও তার unit test যোগ হয়েছে। Version **1.4.1 / code 10**, package ও আগের test APK-এর signing certificate এক। Dependencies, resources ও build configuration অপরিবর্তিত। ZIP alignment এবং signing verification পাস। + +কোনো Android ফোন বা emulator সংযুক্ত নেই। Notification tap latency, install/update ও লোকাল অডিও playback-এর device test করা হয়নি; তাৎক্ষণিক audible playback-এর নিশ্চয়তা দেওয়া হচ্ছে না। Network buffering চললে Play চাপার পরে মিডিয়া শুরু হতে সময় লাগতে পারে। Offline lint dependency সীমার কারণে lintVital বাদ ছিল, lint পাস দাবি করা হচ্ছে না। + +## ফোনে সংক্ষিপ্ত পরীক্ষা +১. চলমান অডিও ও ভিডিওতে notification Pause একবার চাপুন; তারপর Play একবার। দ্রুত পরপর পৃথক ট্যাপেও কিউ যেন Next না হয়। +২. Buffering ও নতুন ভিডিও resolving অবস্থায় Pause দিয়ে অপেক্ষা করুন; স্ট্রিম তৈরি হলে নিজে থেকে বাজা উচিত নয়। Headset single/double-tap, Previous/Next ও screen-off controls-ও দেখুন। +৩. Downloads এবং Audio Downloads-এ একাধিক সম্পন্ন অডিওর আলাদা আলাদা আইটেম চাপুন; প্রতিবার ট্যাপ করা লোকাল ফাইলই যেন চালু হয়। Offline অবস্থায়ও দেখুন। Missing file, paused/incomplete download ও video/AV download যাচাই করুন। +৪. PiP, home feed, swipe seek ও downloads-এর আগের আচরণ দেখুন। + +## সোর্স থেকে বিল্ড +JDK 21 ও Android SDK দিয়ে নিজের local.properties তৈরি করুন। + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest +``` + +এখানে ব্যবহৃত command: + +```sh +JAVA_HOME=/workspace/.jdk/temurin-21 ANDROID_HOME=/workspace/android-sdk ./gradlew --offline :app:assembleCompactTest :app:testCompactTestUnitTest --rerun-tasks -x :app:lintVitalAnalyzeCompactTest -x :app:lintVitalReportCompactTest -x :app:lintVitalCompactTest +``` + +ZIP-এ editable source, Gradle wrapper, vendored libraries, unit tests এবং `verification/notification-first-tap/`-এ যাচাইয়ের প্রমাণ আছে। Signing key, APK, machine-local SDK path ও build caches নেই। সোর্স থেকে নিজের build-এর signing key আলাদা হতে পারে। Historical scope comparison-এর জন্য আগের source ZIP লাগে, APK build বা JVM tests-এর জন্য নয়। diff --git a/PIP_FIX_BN.md b/PIP_FIX_BN.md new file mode 100644 index 0000000..d7924be --- /dev/null +++ b/PIP_FIX_BN.md @@ -0,0 +1,35 @@ +# SparkTube — পপআপ/PiP ভিডিও সংশোধন + +## কী ঠিক করা হয়েছে +আগের কোডে `onPause()`-এর সময় PiP flag এখনো false থাকলে ভিডিওর TextureView থেকে player বিচ্ছিন্ন হতো। PiP callback শুধু layout বদলাত; player ফের জোড়া দিত না। ফলে সিস্টেমের পপআপ controls থাকলেও ভিডিওর আউটপুট হারানোর পথ ছিল। এটি কোডভিত্তিক diagnosis; ফোনে সমস্যাটি reproduce করা হয়নি। অ্যাপে আলাদা floating-overlay service নেই: Android PiP একই PlayerActivity/TextureView ব্যবহার করে। + +এখন pause-এর সময় আউটপুট রাখা হয়; activity সত্যিই অদৃশ্য হলে `onStop()`-এ detach হয়। `onStart()`, `onResume()` ও PiP-entry callback আউটপুট নিশ্চিত করে। PiP চলাকালে orientation/configuration callback আর সাধারণ watch-page layout বসাতে পারে না; ভিডিও পুরো ছোট উইন্ডোতে Fit হিসেবে থাকে। পপআপে app-এর controller বন্ধ, সিস্টেমের PiP/media-session controls অক্ষত; বের হলে স্বাভাবিক controller ও portrait/landscape layout ফেরে। Stop-এ PiP flag পরিষ্কার হয়; destroy-এ অতিরিক্ত detach নিরাপত্তা আছে। আগের surface-owner identity guard mini-player-এর নতুন surface ভুল করে বিচ্ছিন্ন হওয়া আটকায়। + +## যা অপরিবর্তিত +versionName **1.4.1**, versionCode **10**, package `com.sparktube.app` এবং আগের test APK-এর signing certificate একই। আগের audio-downloads source ZIP-এর সঙ্গে সব app source, resource, library ও Gradle file তুলনায় কেবল `PlayerActivity.kt` বদলেছে। Player engine, TextureView configuration, gestures ও controls-এর bindings, notification service, compact Audio/Video switch, Library এবং audio/video download UI অপরিবর্তিত। কোনো dependency বা বড় asset যোগ হয়নি। + +APK **10,303,732 bytes**, প্রায় **10.30 MB**; আগের APK থেকে **-100 bytes**। এটি আগের মতো debug-certificate-এ signed compact test build; production release নয়। + +## যাচাই ও সীমা +Build সফল, **46 JVM unit tests** এবং **22 source/XML regression checks** পাস। Package/version, আগের certificate-এর সঙ্গে মিল, APK signature এবং ZIP alignment যাচাই হয়েছে। `verification/pip-fix/`-এ log, diff ও summary আছে। এই source checks Android lifecycle বা ভিডিও rendering-এর runtime test নয়। আগের audio verifier-এর পুরোনো two-file-only historical diff এই নতুন পরিবর্তনের জন্য প্রযোজ্য নয়; তার চারটি feature check চালানো হয়েছে, আর নতুন verifier সর্বশেষ audio-downloads ZIP-এর সঙ্গে সম্পূর্ণ app-file comparison করেছে। + +কোনো physical Android device বা emulator সংযুক্ত নেই। ফোনে install, PiP-তে সত্যিকারের চলমান ছবি, pause/play, ফিরে আসা, resize, notification বা gestures-এর end-to-end test হয়নি। তাই ফোনে সমস্যাটি নিশ্চিতভাবে সেরেছে বলা যাচ্ছে না। Offline cache-এ lint dependency না থাকায় lintVital tasks বাদ দেওয়া হয়েছে; lint পাস দাবি করা হচ্ছে না। + +## ফোনে ছোট পরীক্ষা +ভিডিওতে চলমান ছবি দেখা গেলে Home চাপুন এবং PiP button দিয়েও আলাদাভাবে পরীক্ষা করুন। পপআপে ছবি চলতে থাকা, pause/play, ছোট-বড় করা, পূর্ণ প্লেয়ারে ফেরা, portrait/landscape এবং close করে app খুললে mini-player দেখা যাচাই করুন। এরপর Audio/Video switch, notification controls ও Library/download pages দেখুন। সমস্যা থাকলে ফোনের model/Android version এবং সমস্যার সময়ের SparkTube crash/logcat output প্রয়োজন হবে। + +## সোর্স থেকে বিল্ড +JDK 21 ও Android SDK প্রয়োজন। নিজের SDK path দিয়ে `local.properties` তৈরি করুন; version অপরিবর্তিত রাখুন। সাধারণ build: + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest +python3 tools/verify-pip-fix.py +``` + +এখানে ব্যবহৃত offline build: + +```sh +JAVA_HOME=/workspace/.jdk/temurin-21 ANDROID_HOME=/workspace/android-sdk ./gradlew --offline :app:assembleCompactTest :app:testCompactTestUnitTest -x :app:lintVitalAnalyzeCompactTest -x :app:lintVitalReportCompactTest -x :app:lintVitalCompactTest +``` + +APK build/JVM tests-এর জন্য পুরোনো ZIP লাগে না। Historical byte-comparison verifier-এর জন্য parent directory-তে আগের audio-downloads ও player-fixed source ZIP প্রয়োজন। Source ZIP-এ editable source, wrapper, dependencies-এর vendored JAR, tests ও verification আছে; local SDK path, cache, build output, APK ও signing key নেই। APK ও ZIP-এর SHA-256 আলাদা manifest-এ দেওয়া হয়েছে। diff --git a/PLAYER_CORRECTION_BN.md b/PLAYER_CORRECTION_BN.md new file mode 100644 index 0000000..6d49beb --- /dev/null +++ b/PLAYER_CORRECTION_BN.md @@ -0,0 +1,54 @@ +# SparkTube 1.4.1 — প্লেয়ার সংশোধন + +এই সংশোধনটি আগের `SparkTube-1.4.1-optimized-test.apk`-এর ওপর ভিত্তি করে। সংস্করণ অপরিবর্তিত: `versionName 1.4.1`, `versionCode 10`। নতুন আউটপুটের নাম `SparkTube-1.4.1-player-fixed-test.apk`। এটি debug-key-signed টেস্ট বিল্ড, production release নয়। আগের নোটের চেয়ে বর্তমান প্লেয়ারের আচরণের জন্য এই নোট প্রযোজ্য। + +## কী বদলেছে + +ভিডিও প্লেয়ারে বামে Previous, মাঝখানে বড় Play/Pause এবং ডানে Next। Media3 1.3.1-এর standard controller ছোট উচ্চতার প্লেয়ারকে minimal mode-এ নিয়ে `exo_prev` ও `exo_next` লুকায়; স্ক্রিনশটে শুধু মাঝের Pause থাকার সঙ্গে এটি মিলে যায়। এবার নিজস্ব controller layout ও queue-button ID ব্যবহার করা হয়েছে, যাতে সেই নিয়ম বাটন দুটো লুকাতে না পারে। Native Play/Pause, seekbar, সময় ও settings রাখা হয়েছে। কিউয়ের অবস্থা বদলালে বাটনের enabled state আপডেট হয়। Next সরাসরি পরের queued item চালায়—১০ সেকেন্ড seek নয়। পরের item না থাকলে Next দৃশ্যমান কিন্তু ম্লান ও নিষ্ক্রিয় থাকে। প্রথম item-এ Previous বর্তমান item শুরু থেকে চালানোর আগের আচরণ রাখে। + +দুই ভিউতেই Audio/Video switch এখন দৃশ্যত 80×32dp গাঢ় pill, 19dp আইকন এবং ছোট সবুজ active segment। ট্যাপের জায়গা 92×48dp রাখা হয়েছে। বর্তমান মোড ও বদলানোর দিক accessibility description-এ উল্লেখ আছে। + +কালো ভিডিওর সম্ভাব্য rendering-path সমস্যার জন্য watch-page `TextureView` বদলে native `SurfaceView` করা হয়েছে। একই ExoPlayer-এর mini-player ও watch-player সংযোগে একটিমাত্র active view রাখা হয়েছে; Media3-এর `PlayerView.switchTargetView` দিয়ে নতুন output আগে যুক্ত করে পুরোনো view ছাড়ানো হয়। একই view পুনরায় attach ও পুরোনো Activity callback-এ অপ্রয়োজনীয় output পরিবর্তন এড়ানো হয়েছে। + +**কালো ভিডিওর একমাত্র মূল কারণ নিশ্চিত হয়নি।** ডিভাইসের decoder/stream/rendering লগ বা সরাসরি playback না দেখে screenshot থেকে সেই নিশ্চয়তা দেওয়া যায় না। Surface ও handoff সংশোধন বাস্তব ফোনে যাচাই প্রয়োজন; কেবল build সফল হওয়া ভিডিও দৃশ্যমান হওয়ার প্রমাণ নয়। + +## ডাউনলোডের নতুন হিসাব ও মেনু + +সক্রিয় download row-তে downloaded/total MB, বর্তমান MB/s এবং আনুমানিক বাকি সময় দেখানো হয়। একক decimal megabyte: 1 MB = 1,000,000 bytes; MB/s-কে Mbps হিসেবে ভুল লেবেল দেওয়া হয় না। UI refresh-এর সংখ্যা দিয়ে speed sample বদলায় না; transferred-byte updates এবং monotonic সময় থেকে rate গণনা হয়। Pause/resume-এ sample reset হয়; stall হলে পুরোনো speed শূন্য হয়। ETA বাকি bytes/current rate থেকে ঊর্ধ্বমুখী পূর্ণ সেকেন্ডে হিসাব হয় এবং `~` দিয়ে আনুমানিক বোঝানো হয়—ভবিষ্যতের নেটওয়ার্ক গতি নিশ্চিত করা যায় না। + +Audio+Video জোড়ার উভয় অংশের bytes/total একসঙ্গে ধরা হয়। কোনো অংশের total অজানা থাকলে সম্পূর্ণ size ও ETA অজানা দেখানো হয়, বানানো percentage/সময় নয়। HTTP full-response Content-Length পাওয়া গেলে ব্যবহার হয়; এক-byte range probe-এর Content-Length-কে মোট size ধরা হয় না। Completed row-তে আসল ফাইলের মোট size এবং ডানদিকে 3-dot মেনু থাকে: Play, Share, Delete। Delete আগের confirmation ব্যবহার করে; AV sharing আগের মতো দুইটি আলাদা track file দেয়, নতুন muxing ফিচার যোগ হয়নি। + +## কী অক্ষুণ্ণ রাখা হয়েছে + +Double-tap ±10-second seek এবং volume/brightness gesture কোড আছে। Notification/lock-screen queue-aware Previous/Play-Pause/Next service, download sharing এবং swipeable Library-এর কোড আগের source ZIP-এর সঙ্গে অপরিবর্তিত থাকার পরীক্ষা করা হয়েছে। Download engine ও download-row UI কেবল নতুন অনুমোদিত metrics/menu কাজের জন্য সংশোধিত। বিদ্যমান vendored library JAR অপরিবর্তিত। অ্যাপের R8 minification ও resource shrinking বন্ধ; কোনো feature/dependency/ABI/ভাষা বাদ দেওয়া হয়নি। বাস্তবে সব feature end-to-end চালিয়ে পরীক্ষা হয়েছে—এমন দাবি করা হচ্ছে না। + +## পরীক্ষা ও সীমাবদ্ধতা + +বর্তমান APK 10.30 MB (10,300,268 bytes)। 31টি JVM test এবং ৯টি structural test পাস করেছে; failure/error/skipped শূন্য। APK signature ও zip alignment যাচাই সফল; manifest ও signing certificate আগের optimized APK-এর সঙ্গে একই। + +`verification/player-correction/`-এ বর্তমান build log, structural test output, JVM test XML, manifest, signature ও artifact summary থাকবে। কাঠামোগত পরীক্ষা render/touch/device test নয়। পুরোনো `verification/size-optimization/` আগের বিল্ডের ঐতিহাসিক প্রমাণ, বর্তমান ফল নয়। + +কোনো physical Android device সংযুক্ত নেই; `adb devices`-এ কোনো device নেই। Android emulator-ও এখানে চালু নেই। তাই ফোনে install/update, ছবিসহ playback, fullscreen/PiP, gesture touch, lock-screen rendering ও download end-to-end পরীক্ষা করা যায়নি। + +অফলাইন cache-এ `lint-gradle:31.6.1` অনুপস্থিত থাকায় আগের মতো lintVital task বাদ দিয়ে বিল্ড করা হয়েছে; lint সফল হয়েছে বলা হচ্ছে না। পুরোনো Kotlin/D8 metadata ও deprecation warning থাকতে পারে। + +## আবার বিল্ড + +JDK 21 এবং Android SDK 35 ব্যবহার করুন। নিজের SDK path দিয়ে `local.properties` তৈরি করুন অথবা `ANDROID_HOME` সেট করুন। স্বাভাবিকভাবে: + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest +python3 tools/verify-player-correction.py +``` + +এই পরিবেশে ব্যবহৃত অফলাইন কমান্ড: + +```sh +./gradlew --offline --no-daemon \ + :app:assembleCompactTest :app:testCompactTestUnitTest \ + -x :app:lintVitalAnalyzeCompactTest \ + -x :app:lintVitalReportCompactTest \ + -x :app:lintVitalCompactTest +``` + +APK: `app/build/outputs/apk/compactTest/app-compactTest.apk`। Editable source ZIP-এ build/cache, local SDK path বা signing key নেই। ভিন্ন কম্পিউটারে debug key আলাদা হলে তৈরি APK-এর signature-ও আলাদা হবে। বিতরণের APK ও source ZIP-এর checksum আলাদা SHA-256 manifest-এ দেওয়া হয়েছে। diff --git a/PORTRAIT_SEEK_BN.md b/PORTRAIT_SEEK_BN.md new file mode 100644 index 0000000..bd9c2cb --- /dev/null +++ b/PORTRAIT_SEEK_BN.md @@ -0,0 +1,28 @@ +# পোর্ট্রেট seek: intentional gesture রাখুন, accidental touch আটকান + +পোর্ট্রেটে ভিডিওর ওপর intentional horizontal swipe seek এবং বাম/ডান অর্ধে double-tap করে −১০/+১০ সেকেন্ড seek ফিরিয়ে দেওয়া হয়েছে। Single tap শুধু controls দেখায়/লুকায়। Surface swipe আগের মতো ১৪dp movement ও horizontal dominance শনাক্ত হওয়ার পরে preview দেখায়; আঙুল ছাড়লে seek হয়। শুরু ও শেষের orientation আলাদা হলে surface seek commit হয় না। + +## নিচের seek bar + +`DeliberateSeekTimeBar` native Media3 time bar-এর subclass। শুধু portrait touch-এ প্রথম DOWN আটকে রাখে। অন্তত ১৪dp (বা system touch slop, যেটি বেশি) এবং স্পষ্ট horizontal movement হলে buffered DOWN ও MOVE native time bar-এ পাঠায়। তাই tap, ছোট jitter, vertical বা ambiguous diagonal drag thumb সরায় না বা playback seek করে না। Meaningful non-horizontal drag একবার reject হলে একই touch sequence পরে horizontal হয়ে scrub করতে পারে না। Multi-touch, CANCEL, rotation ও detach-এ pending/active portrait scrub বাতিল হয়। Native scrub listeners, preview ও release-to-seek behavior-ই ব্যবহার করা হয়; আলাদা playback seek loop নয়। Keyboard ও accessibility actions native Media3-তেই থাকে। + +Touch ownership শুরুতে নির্ধারিত হয়। ভিডিওর surface থেকে bar-এ আঙুল সরানো native timebar drag শুরু করে না; সেটি intentional surface gesture হিসেবেই থাকে। আসল buttons তাদের আগের touch flow পায়। Landscape-এ timebar সরাসরি native `onTouchEvent` ব্যবহার করে; একই native style, dimensions এবং surface seek/brightness/volume আচরণ রাখা হয়েছে। + +## ভিডিও কার্ডের Download + +ভিডিও কার্ডের three-dot menu-তে Download যোগ করা হয়েছে। বিদ্যমান download flow, progress এবং library UI ব্যবহার করা হয়; অন্য menu actions রাখা হয়েছে। + +## Build ও যাচাই + +versionName **1.4.1**, versionCode **10**। Build/signing configuration এবং dependency অপরিবর্তিত। Source ZIP-এ keystore/password নেই; অন্য পরিবেশে একই signing certificate পেতে আগের signing key প্রয়োজন। SDK path নিজের `local.properties`-এ দিন। + +এই পরিবেশে: + +```bash +JAVA_HOME=/workspace/.jdk/temurin-21 ANDROID_HOME=/workspace/android-sdk ./gradlew --offline :app:assembleCompactTest :app:testCompactTestUnitTest -x :app:lintVitalAnalyzeCompactTest -x :app:lintVitalReportCompactTest -x :app:lintVitalCompactTest +python3 tools/package-gestures-download.py +``` + +Build/test/signature/version/archive verification-এর ফল `verification/gestures-download/verification-summary.json`-এ থাকে। Local JVM tests pure gesture math/gate/policy পরীক্ষা করে; native Android touch integration পরীক্ষা করে না। Android ফোন বা emulator সংযুক্ত নেই। Runtime touch, visual overlap, download network flow, rotation ও landscape regression বাস্তব ডিভাইসে পরীক্ষা করা হয়নি। Lint এই build-এ বাদ দেওয়া হয়েছে। + +ফোনে পরীক্ষা: portrait surface swipe, দুই পাশে double-tap, single tap; bar tap/jitter/vertical/diagonal drag বনাম deliberate horizontal drag; surface থেকে bar-এ drag; CANCEL/multi-touch/rotation; landscape controls/seek/volume/brightness; এবং card menu Download থেকে progress ও library flow। diff --git a/RENDER_CONTROLS_BN.md b/RENDER_CONTROLS_BN.md new file mode 100644 index 0000000..67dcadc --- /dev/null +++ b/RENDER_CONTROLS_BN.md @@ -0,0 +1,37 @@ +# SparkTube 1.4.1 — ভিডিও রেন্ডারিং ও কন্ট্রোল সংশোধন + +এই নোটটি আগের PLAYER_CORRECTION_BN.md-এর পরবর্তী সংশোধনের বিবরণ। versionName **1.4.1**, versionCode **10** অপরিবর্তিত। এটি আগের পরীক্ষামূলক APK-এর একই debug certificate দিয়ে সই করা compact test build; production/release signing দাবি করা হচ্ছে না। + +## কালো ভিডিওর জন্য পাওয়া সরাসরি কোডের ত্রুটি + +আগের QueueForwardingPlayer-এ `object : Player.Listener by listener` ব্যবহার করা হয়েছিল। এই প্রজেক্টের Kotlin compiler Java interface-এর default callback-গুলোকে ওই delegation দিয়ে ফরওয়ার্ড করেনি। কম্পাইলড bridge class-এ শুধু `onAvailableCommandsChanged` ও `onEvents` ছিল; `onRenderedFirstFrame`, `onVideoSizeChanged`, `onTracksChanged` ছিল না। + +Media3 PlayerView প্রথম ফ্রেমের callback পেলে তার কালো shutter সরায়। আগের bridge callback গিলে ফেললে, ডিকোডার ভিডিও তৈরি করলেও shutter কালো থেকে যেতে পারে। তাই কেবল SurfaceView handoff বদলানো যথেষ্ট ছিল না। নতুন JVM regression test পুরোনো delegation-এ first-frame callback হারানো এবং নতুন bridge-এ পৌঁছানো পুনরুৎপাদন করে। + +সংশোধনে PlayerView সরাসরি raw ExoPlayer-এ যুক্ত হয়েছে; কিউ-সচেতন media session আলাদা facade ব্যবহার করে। সেই facade-এর জন্য PlayerListenerBridge.java Media3 1.3.1-এর ৩৭টি callback স্পষ্টভাবে ফরওয়ার্ড করে। API-coverage test প্রতিটি callback যাচাই করে। + +এটি কোড/bytecode-এ নিশ্চিত ত্রুটি এবং সংশোধন; মালিকের ফোনে কালো স্ক্রিন সেরে গেছে—এমন দাবি নয়। + +## কন্ট্রোল ও রেন্ডারিং + +Previous / বড় Play-Pause / Next এখন activity_player.xml-এর সরাসরি sibling overlay: 52dp / 76dp / 52dp। Media3-এর center/minimal-mode container বা optional findViewById-এর ওপর এই বাটনগুলো নির্ভর করে না। একই সময়ে দুটো Play/Pause দেখানো এড়াতে Media3 template থেকে center transport সরানো হয়েছে; seekbar, সময় ও settings রাখা হয়েছে। Next পরের queued item চালায়; দশ সেকেন্ড seek করে না। প্রথম আইটেমে Previous বর্তমান ভিডিও শুরু থেকে চালায়। + +Main ও mini player-এ TextureView ব্যবহৃত হয়েছে; output owner ও stale detach guard রাখা হয়েছে। Decoder fallback চালু। শুধু READY/isPlaying-কে ভিডিও সফল বলা হয় না: দৃশ্যমান, READY, play-requested অবস্থায় বাস্তব first-frame না এলে একবার output rebind, তারপর progressive source fallback, শেষে স্পষ্ট Retry error। ব্যাকগ্রাউন্ডে/পজ অবস্থায় এই recovery হয় না। source rebuild position ও play/pause intent ধরে রাখে। Retry কিউ মুছে ফেলে না এবং local download-কে online URL হিসেবে resolve করে না। + +## যেসব ফিচার রাখা হয়েছে + +Compact dark/green Audio–Video switch, বাম/ডানে double-tap-এ ১০ সেকেন্ড seek, ডানে vertical volume ও বামে brightness gesture রাখা হয়েছে; standalone দশ সেকেন্ডের বাটন নেই। Lock-screen/notification-এর Previous–Play/Pause–Next queue facade-এ আছে। Swipeable Library, download-এর downloaded/total MB, MB/s, আনুমানিক ETA এবং তিন-ডট Play/Share/Delete-এর source আগের build-এর সঙ্গে byte-for-byte তুলনা করা হয়েছে। Production dependency, সংস্করণ, resource/feature shrinking policy বদলানো হয়নি। + +## পরীক্ষা ও পুনরায় বিল্ড + +JVM test results, structural-test log, compiled APK XML, signature, zipalign এবং version metadata `verification/render-controls/`-এ আছে। এগুলো ফোনে চালানো rendering/touch/instrumentation test নয়। ADB-তে কোনো Android device যুক্ত নেই; emulator-এও চালানো হয়নি। তাই আসল ফোনে video frames, rotation, mini-player, PiP, gesture এবং lock-screen controls যাচাই এখনও প্রয়োজন। Lint-এর অবস্থা verification-summary.json-এ আলাদা দেওয়া আছে। + +JDK 17 ও Android SDK 35 দিয়ে, SDK location স্থানীয়ভাবে সেট করে: + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest \ + -x lintVitalAnalyzeCompactTest -x lintVitalReportCompactTest -x lintVitalCompactTest +python3 tools/verify-player-correction.py +``` + +পুরোনো source ZIP পাশে না থাকলে byte-for-byte baseline comparison test skip হবে; অন্য পরীক্ষা চলবে। সোর্স ZIP-এ local.properties, build cache বা signing keystore নেই। একই versionCode রাখা হয়েছে, তাই পরীক্ষা করার সময় ফোনে সঠিক নতুন APK install হয়েছে কি না নিশ্চিত করা দরকার। স্বাক্ষর না মেললে এই test APK দিয়ে অন্য certificate-এর app-এর ওপর update হবে না; ডেটা হারানোর ঝুঁকি থাকায় যাচাই ছাড়া uninstall করা উচিত নয়। diff --git a/RESPONSIVE_CONTROLS_BN.md b/RESPONSIVE_CONTROLS_BN.md new file mode 100644 index 0000000..16d1b7e --- /dev/null +++ b/RESPONSIVE_CONTROLS_BN.md @@ -0,0 +1,41 @@ +# SparkTube — দ্রুত হোম ফিড, মিডিয়া কন্ট্রোল ও সোয়াইপ সিক + +## নতুন আচরণ +অ্যাপ খোলার সময় আগের সফল হোম ফিডের সীমিত, দেশভিত্তিক cache থেকে ভিডিও কার্ড আগে দেখানো হয়; নতুন ফিড পেছনে refresh হয়। Cache-এ শুধু ভিডিওর তালিকার তথ্য ও thumbnail URL থাকে, playable stream URL নয়। Fresh install বা cache না থাকলে তিনটি feed source একসঙ্গে লোড হয় এবং প্রথম সফল source-এর ফল বাকি source ও পূর্ণ recommendation ranking শেষ হওয়ার আগেই দেখানো হয়। Refresh ব্যর্থ হলে ব্যবহারযোগ্য পুরোনো তালিকা মুছে ফেলা হয় না। প্রথমবারের network request বা uncached thumbnail-কে offline/তাৎক্ষণিক বলে দাবি করা হচ্ছে না। + +নোটিফিকেশনের Previous / Play-Pause / Next রাখা হয়েছে। বাফারিং চলাকালে play/pause এখন `isPlaying` নয়, ব্যবহারকারীর `playWhenReady` intent ধরে কাজ করে। নতুন ভিডিওতে Next/Previous চাপলে আগের ভিডিওর অডিও থামে এবং session-এ নতুন শিরোনাম ও সত্যিকারের loading/buffering অবস্থা স্ট্রিম URL পাওয়ার আগেই পাঠানো হয়। লোডিংয়ের মধ্যে Pause দিলে নতুন source তৈরি হওয়ার পর সেটি নিজে থেকে আর বাজবে না। দ্রুত পরপর Next/Previous চাপলে বাতিল পুরোনো কাজ নতুন নির্বাচনের state মুছতে পারে না। প্রকৃত ভিডিও/অডিও শুরু হতে নেটওয়ার্ক সময় লাগতে পারে; READY বা playing-এর মিথ্যা অবস্থা দেখানো হয় না। + +ভিডিও পেজের সক্রিয় “Downloading · …%” বাটনে চাপলে Library → Downloads খুলবে এবং বিদ্যমান live progress refresh চলবে। ডাউনলোড না চললে আগের quality/audio-video download sheet এবং সম্পূর্ণ ডাউনলোডের delete confirmation থাকে। + +ডানে অনুভূমিক সোয়াইপে ধীরে সামনে, বামে পিছনে seek preview হয়; আঙুল ছাড়লে সেই অবস্থানে যাওয়া হয়। ছোট নড়াচড়া ও দিক নির্বাচন আলাদা করা হয়েছে, যাতে ওপর-নিচের brightness/volume gesture-এর সঙ্গে না মেশে। আগের double-tap ±১০ সেকেন্ডও থাকে। Cancel হলে seek হয় না। + +## যা সংরক্ষিত +সংস্করণ **1.4.1**, versionCode **10**, package `com.sparktube.app` এবং আগের test APK-এর signing certificate অপরিবর্তিত। Compact test build; production release নয়। PiP lifecycle/rendering fix, audio/video downloads, কিউ transport, বিদ্যমান controls ও resources রাখা হয়েছে। নতুন dependency বা বড় asset নেই। APK **10,316,436 bytes** (প্রায় **10.32 MB**); আগের PiP test APK থেকে **+12,704 bytes**। + +## যাচাই ও সীমা +Build সফল; **66 JVM unit tests** এবং **48 source/XML regression checks** পাস। Source checks Android runtime test নয়। সর্বশেষ PiP source ZIP-এর সঙ্গে app files তুলনা করে শুধু অনুমোদিত playback, navigation ও gesture files বদলানোর যাচাই আছে। পুরোনো আপডেটগুলোর historical “অন্য কোনো ফাইল বদলাবে না” assertion বর্তমান scope-এর জন্য বাদ দিয়ে নতুন latest-source scope audit চালানো হয়েছে; feature checks রাখা হয়েছে। Signature/certificate match, version/package এবং ZIP alignment যাচাই হয়েছে। + +কোনো physical Android device বা emulator সংযুক্ত নেই; ফোনে install, notification latency, swipe feel, PiP rendering বা download navigation-এর end-to-end পরীক্ষা করা হয়নি। তাই ফোনে নিশ্চিত তাৎক্ষণিক প্রতিক্রিয়া দাবি করা হচ্ছে না। Offline lint dependency না থাকায় lintVital tasks বাদ; lint পাস দাবি নেই। Logs, diffs, test XML ও summary `verification/notification-response/`-এ আছে। + +## ফোনে সংক্ষিপ্ত পরীক্ষা +০. একবার Home লোড করে অ্যাপ সম্পূর্ণ বন্ধ করে ফের খুলুন; আগের কার্ড আগে দেখা এবং পেছনে refresh পরীক্ষা করুন। Slow/offline network, fresh install, country change ও pull-to-refresh-ও দেখুন; অন্য দেশের cache যেন না আসে। +১. চলমান ও buffering ভিডিও/গানে notification Play/Pause দ্রুত চাপুন; পজ দিলে নেটওয়ার্ক ফেরার পর নিজে থেকে বাজে কি না দেখুন। +২. ভিডিও কিউতে Next/Previous দ্রুত চাপুন; নতুন শিরোনাম/loading, সঠিক শেষ নির্বাচন এবং আগের অডিও বন্ধ হওয়া দেখুন। কিউয়ের শুরু/শেষও পরীক্ষা করুন। +৩. “Downloading · …%” চাপুন; Downloads-এ বর্তমান অগ্রগতি এবং pause/resume/cancel দেখুন। Idle download button-এর sheet ও completed download delete অপরিবর্তিত আছে কি না দেখুন। +৪. ডানে/বামে ধীরে সোয়াইপ, seek preview/release, শুরু/শেষ সীমা, double-tap ±১০ সেকেন্ড, brightness/volume এবং portrait/landscape পরীক্ষা করুন। +৫. Home/PiP, full-player-এ ফেরা এবং audio/video download পরীক্ষা করুন। + +## সোর্স থেকে বিল্ড +JDK 21 ও Android SDK দিয়ে নিজের `local.properties` বানিয়ে চালান: + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest +``` + +এখানে ব্যবহৃত offline command: + +```sh +JAVA_HOME=/workspace/.jdk/temurin-21 ANDROID_HOME=/workspace/android-sdk ./gradlew --offline :app:assembleCompactTest :app:testCompactTestUnitTest -x :app:lintVitalAnalyzeCompactTest -x :app:lintVitalReportCompactTest -x :app:lintVitalCompactTest +``` + +ZIP-এ editable source, Gradle wrapper, vendored libraries, unit tests ও verification আছে; build output/cache, local SDK path, APK ও signing key নেই। APK ও ZIP-এর SHA-256 আলাদা manifest-এ আছে। ঐতিহাসিক diff verifier চালাতে আগের source ZIP parent folder-এ লাগে; APK build বা JVM tests-এর জন্য লাগে না। diff --git a/SIZE_OPTIMIZATION_BN.md b/SIZE_OPTIMIZATION_BN.md new file mode 100644 index 0000000..8a59275 --- /dev/null +++ b/SIZE_OPTIMIZATION_BN.md @@ -0,0 +1,58 @@ +# SparkTube 1.4.1 — ছোট টেস্ট APK + +এই বিল্ডে অ্যাপের ভার্সন বাড়ানো হয়নি: `versionName 1.4.1`, `versionCode 10`, প্যাকেজ `com.sparktube.app`। আগের টেস্ট APK-এর একই signing certificate ব্যবহার করা হয়েছে। + +## সাইজ + +| ফাইল | প্রকৃত সাইজ | +| --- | ---: | +| আগের `SparkTube-1.4.1-test-debug.apk` | 14,995,725 bytes — 15.00 MB | +| নতুন `SparkTube-1.4.1-optimized-test.apk` | 10,287,944 bytes — 10.29 MB | +| কমেছে | 4,707,781 bytes — 31.39% | + +এখানে MB = 1,000,000 bytes; এগুলো ডাউনলোডযোগ্য APK-এর সাইজ, ফোনে ইনস্টল হওয়ার পরের স্টোরেজ নয়। + +## নতুন কন্ট্রোল + +লকস্ক্রিন/মিডিয়া নোটিফিকেশনের দৃশ্যমান হার্ট অ্যাকশন সরিয়ে native Previous, Play/Pause ও Next ব্যবহার করা হয়েছে। MediaSession এখন PlayerView-এর মতো একই স্থিতিশীল, অ্যাপ-কিউ-সচেতন প্লেয়ার ব্যবহার করে। Next বর্তমান গানের ১০ সেকেন্ড এগোয় না; পরের queued গান/ভিডিও চালায়। ভিডিওতে অ্যাপের কিউ এবং অডিওতে ExoPlayer-এর প্রকৃত playlist অনুযায়ী কমান্ডের অবস্থা আপডেট হয়। পরের আইটেম না থাকলে Next নিষ্ক্রিয় থাকে। Previous আগের আইটেমে যায়; প্রথম ভিডিওতে বর্তমান ভিডিও শুরু থেকে চালানোর আগের আচরণ থাকে। অ্যাপের ভেতরের Favorite বাটন ও Favorites তালিকা অক্ষুণ্ণ। + +ভিডিও কন্ট্রোলে দুই পাশে পৃথক গোলাকার Previous/Next এবং মাঝখানে বড় Play/Pause আছে। দৃশ্যমান ১০-সেকেন্ড rewind/forward বাটন নেই; ফাঁকা ভিডিও অংশে ডাবল-ট্যাপ সিক আগের মতো থাকে। + +ভিডিও ও অডিও দুই ভিউয়ের ওপরের Audio/Video সুইচ একই ছোট গাঢ় rounded pill: সক্রিয় মোড সবুজ বৃত্তে, অন্য মোড পাশাপাশি ধূসর আইকনে। আগের switch action, position/queue সংরক্ষণ ও gesture exclusion রাখা হয়েছে। আগের volume/brightness gestures, swipeable Library এবং download pause/resume, progress, speed ও sharing কোড বাদ দেওয়া হয়নি। আগের download সীমাবদ্ধতাগুলো `BUILD_NOTES_BN.md`-এ আছে। + +## নিরাপদে কীভাবে ছোট করা হয়েছে + +আলাদা `compactTest` build type যোগ করা হয়েছে। সাধারণ `debug` ও `release` build type-এর আচরণ বদলানো হয়নি। `compactTest`-এ non-debug D8/L8 compilation ও Android resource packaging optimization ব্যবহার হয়; অ্যাপের R8 minification/obfuscation এবং resource shrinking বন্ধ। কোনো feature, dependency, ABI বা ভাষার তালিকা কেটে ছোট করা হয়নি। সব 6,070টি আগের Android resource name আছে; নতুন ছোট XML drawable তিনটি যোগ হয়েছে। AAPT2 কিছু resource file path ছোট/একত্র করেছে—এটি resource name বা ফিচার মুছে ফেলা নয়। Toolchain-এর স্বাভাবিক L8 প্রক্রিয়া অব্যবহৃত desugared runtime helper বাদ দিতে এবং D8 synthetic class বদলাতে পারে। মূল third-party library-র সাধারণ ক্লাস বাদ যায়নি; vendored JAR অপরিবর্তিত। + +এই APK `android:debuggable=false`, অর্থাৎ debugger attach করার জন্য নয়। কিন্তু `BuildConfig.DEBUG=true` রাখা হয়েছে, যাতে আগের টেস্ট বিল্ডের update-check ও crash-log আচরণ একই থাকে। এটি এখনও debug-key-signed টেস্ট APK; প্রকাশনার production signing key নয়। + +## যাচাই + +- `assembleCompactTest` ও `testCompactTestUnitTest` সফল। ১৫টি JVM টেস্ট পাস: আগের gesture গণনার ৭টি এবং queue boundary-এর নতুন ৮টি; failures/errors/skipped শূন্য। +- ১৩৩টি XML parse হয়েছে। APK signature এবং zip alignment যাচাই সফল। APK-এর ভেতরের version, package, permissions ও manifest component list যাচাই হয়েছে; permissions/components অপরিবর্তিত। +- Certificate SHA-256: `40f61a2530f18ff692b47b627fa45eae3fd66ff95c076a4b461086128a5db766` — আগের টেস্ট APK-এর সঙ্গে একই। +- APK SHA-256: `9c4d5bec11dc003237859a7ee10cb1378fd9bb8bb2f778e66a15133d2b7671f1`। + +কোনো Android ফোন/emulator যুক্ত ছিল না। তাই ফোনে install/update, বাস্তব লকস্ক্রিনের button rendering, native MediaController interaction, touch dispatch, playback ও download-এর end-to-end পরীক্ষা হয়নি। Queue-policy টেস্ট ও static/compile checks-কে সেই পরীক্ষার বিকল্প বলা হচ্ছে না। `lint-gradle:31.6.1` অফলাইন ক্যাশে অনুপস্থিত থাকায় lint সম্পন্ন হয়নি; সফল অফলাইন build-এ তিনটি lintVital task স্পষ্টভাবে বাদ দেওয়া হয়েছে। বিদ্যমান Kotlin/D8 metadata warning-ও রয়েছে; toolchain বদলানো হয়নি। + +## আবার বিল্ড + +JDK 21, Android SDK 35 ও build-tools 34.0.0 ব্যবহার করা হয়েছে। নিজের SDK path দিয়ে `local.properties` তৈরি করুন অথবা `ANDROID_HOME` সেট করুন। স্বাভাবিক অনলাইন বিল্ড: + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest +``` + +এই পরিবেশে ব্যবহৃত অফলাইন কমান্ড, lint-এর উল্লিখিত সীমাবদ্ধতাসহ: + +```sh +./gradlew --offline --no-daemon \ + :app:assembleCompactTest :app:testCompactTestUnitTest \ + -x :app:lintVitalAnalyzeCompactTest \ + -x :app:lintVitalReportCompactTest \ + -x :app:lintVitalCompactTest +``` + +আউটপুট: `app/build/outputs/apk/compactTest/app-compactTest.apk`। বিতরণের জন্য এর নাম `SparkTube-1.4.1-optimized-test.apk` করা হয়েছে। একই কম্পিউটারের বিদ্যমান debug key পুনঃব্যবহার করা হয়েছে; ZIP-এ signing key নেই। অন্য কম্পিউটারে আলাদা debug key থাকলে সই একই হবে না। আগের অ্যাপের ওপর update করতে signature মিলতে হবে; এই বিতরণকৃত APK-এর ক্ষেত্রে তা যাচাই হয়েছে। + +`tools/apk-size-audit.py` দিয়ে দুটি APK-এর ZIP payload এবং DEX class তুলনা করা যায়। `verification/size-optimization/`-এ সফল বিল্ডের `build-controls-final.log`, দুই test suite-এর XML, APK comparison, source hash baseline, resource dump, manifest এবং signature verification আছে। আগের কয়েকটি অসম্পূর্ণ build attempt-এর log-ও রাখা হয়েছে; চূড়ান্ত ফল `verification-summary.json`-এ। Editable source ZIP-এ project source, Gradle wrapper, libraries, tests ও verification আছে; local SDK path, build/cache directory ও signing key নেই। APK ও ZIP-এর SHA-256 আলাদা manifest-এ দেওয়া হয়েছে। diff --git a/SLEEP_TIMER_BN.md b/SLEEP_TIMER_BN.md new file mode 100644 index 0000000..9bc9e62 --- /dev/null +++ b/SLEEP_TIMER_BN.md @@ -0,0 +1,35 @@ +# SparkTube 1.4.1 — Sleep timer ও দ্রুত অডিও সুইচ + +অডিও প্লেয়ারে সবুজ outlined Timer বাটন আছে। ভিডিও পেজের horizontal action row-তে Background play বাটনের জায়গায় Timer রাখা হয়েছে। দুই বাটন একই Sleep timer selector খোলে: Off, 10, 15, 20, 30, 45 minutes, 1 hour এবং End of video। নির্বাচিত অপশনে চেক চিহ্ন থাকে। ছোট স্ক্রিনে তালিকা স্ক্রল করা যায়। + +টাইমার সেট করলে বাটনে মিনিট:সেকেন্ড কাউন্টডাউন দেখায়। ভিডিওতে প্রতি সেকেন্ডে এবং অডিওতে অর্ধ সেকেন্ড অন্তর লেবেল refresh হয়। একটাই app-wide monotonic deadline ব্যবহৃত হয়: pause, seek, playback speed, পেজ বদল বা অডিও/ভিডিও সুইচে নির্ধারিত সময় নতুন করে শুরু হয় না। Off বাতিল করে, অন্য সময় বাছলে নতুন countdown শুরু হয়। + +সময় শেষ হলে shared player বন্ধ হয়; queue, pending stream resolution ও recovery jobs বাতিল হয় যাতে পরে নিজে থেকে আবার চালু না হয়। খোলা player page বন্ধ হয়। এটি অ্যাপের process জোর করে kill করে না এবং downloads বন্ধ করে না। End of video বর্তমান media শেষ হলে থামায়, next item autoplay করার আগে; নিজে অন্য media বাছলে শুধু End of video সেটিং বাতিল হয়। নির্দিষ্ট মিনিটের countdown queue বদলালেও থাকে। + +উপরের ভিডিও→অডিও সুইচে একই ExoPlayer ও source রাখা হয়। সোর্স reload/prepare/seek করা হয় না, তাই পেজ বদলের জন্য নেটওয়ার্কের অপেক্ষা নেই। অডিও পেজ থেকে একই ভিডিওতে ফিরলেও retained source পুনরায় ব্যবহার হয়। প্রকৃত music-only stream থেকে প্রথমবার ভিডিওতে যাওয়া বা পরে নতুন audio-only source লোড হলে আগের মতো resolving প্রয়োজন হতে পারে। + +## যাচাই + +`verification/sleep-timer/`-এ Gradle build log, JVM tests, source/XML regression checks, APK metadata, signing certificate comparison, zip alignment ও verification summary আছে। Package `com.sparktube.app`, versionName `1.4.1`, versionCode `10`, dependencies, manifest ও compact signing configuration অপরিবর্তিত। আগের video-collapse test APK-এর certificate-এর সঙ্গে মিল পরীক্ষা করা হয়। + +কোনো Android ফোন বা emulator সংযুক্ত নেই। ইনস্টল, বাস্তব ট্যাপ/কাউন্টডাউন, 10–60 মিনিটের background run, screen-off/Doze/OEM battery restriction, PiP, transition speed ও নিরবচ্ছিন্ন playback ডিভাইসে পরীক্ষা করা হয়নি। Listener-based wake alarm ও shared service ticker আছে; Android/OEM scheduling-এর বিলম্বমুক্ত কার্যকারিতা বাস্তব ফোনে যাচাই বাকি। Offline lint dependencies সীমাবদ্ধতার কারণে lintVital tasks বাদ দেওয়া হয়েছে; lint pass দাবি করা হচ্ছে না। + +## ফোনে সংক্ষিপ্ত পরীক্ষা + +1. অডিওতে 10 minutes দিন: 10:00 থেকে কমবে। ভিডিওতে switch করে একই বাকি সময় দেখুন। +2. ভিডিওর Timer থেকে সময় বদলান, Off দিন ও শিট পুনরায় খুলে selected চিহ্ন দেখুন। +3. pause/seek/speed পরিবর্তন ও স্ক্রিন বন্ধ রেখেও নির্বাচিত wall-clock সময় শেষে playback বন্ধ হওয়া দেখুন। +4. দুই গান/ভিডিওর queue-তে End of video দিন: পরেরটি যেন নিজে থেকে না চলে। +5. ভিডিও চলার সময় উপরের audio switch চাপুন: পেজ দ্রুত খুলবে, position/প্লেব্যাক অবস্থা থাকবে; ফিরে আসাও দেখুন। +6. Notification controls, downloads, PiP, collapse, quality selection ও queue navigation-এর পূর্বের আচরণ পরীক্ষা করুন। + +## সোর্স থেকে বিল্ড + +JDK 21 ও Android SDK দিয়ে নিজের `local.properties` তৈরি করুন: + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest +python3 tools/verify-current-update.py +``` + +এই পরিবেশে Gradle `--offline -x :app:lintVitalAnalyzeCompactTest -x :app:lintVitalReportCompactTest -x :app:lintVitalCompactTest` দিয়ে চালানো হয়েছে। ZIP-এ editable source, wrapper, vendored libraries ও tests রয়েছে; SDK path, build caches, APK ও signing key নেই। নিজের signing key দিয়ে rebuild করলে certificate আলাদা হবে। ঐতিহাসিক source-scope audit-এ আগের source ZIP লাগে; APK/JVM build-এ লাগে না। diff --git a/VIDEO_COLLAPSE_BN.md b/VIDEO_COLLAPSE_BN.md new file mode 100644 index 0000000..89b1cc5 --- /dev/null +++ b/VIDEO_COLLAPSE_BN.md @@ -0,0 +1,29 @@ +# SparkTube 1.4.1 — ভিডিও প্লেয়ার ছোট করার বাটন + +ভিডিও প্লেয়ারের উপরের বাঁ কোণে অডিও প্লেয়ারের মতো নিচমুখী শেভরন বাটন যোগ হয়েছে। একই `finish()` অ্যাকশন ভিডিও পেজ বন্ধ করে আগের স্ক্রিনের মিনি প্লেয়ারে ফেরায়; বাটনটি প্লেব্যাক বন্ধ, কিউ পরিবর্তন, অডিও মোডে রূপান্তর বা PiP চালু করে না। বিদ্যমান slide-down transition ও shared-player lifecycle ব্যবহৃত হয়। + +বাটনের টাচ এরিয়া 48×48dp। এটি ভিডিওর ভেতরে থাকে, transport controls লুকালেও দেখা যায়, PiP-এ লুকায় এবং PiP থেকে ফিরলে আবার আসে। Portrait-এ status-bar inset দুবার যোগ করা হয় না; fullscreen-এ cutout/system-bar inset মানা হয়। বাটনটি অডিওর একই down-chevron আইকন ব্যবহার করে; ভিডিওতে contrast-এর জন্য বিদ্যমান dark scrim রাখা হয়েছে। + +## যাচাই + +`verification/video-collapse/`-এ বিল্ড লগ, source/XML checks, JVM test results, APK metadata, signing ও alignment verification এবং পরিবর্তনের diff আছে। Source/XML checks Android UI execution নয়। Package, versionName 1.4.1, versionCode 10, dependencies ও compact-test build configuration অপরিবর্তিত। আগের notification-audio test APK-এর signing certificate-এর সঙ্গে মিল পরীক্ষা করা হয়েছে। অ্যাপের আগের সোর্সের তুলনায় শুধু PlayerActivity.kt, activity_player.xml ও strings_player_controls.xml বদলেছে; playback service, notification, downloads, audio ও queue implementation অপরিবর্তিত। + +কোনো Android ডিভাইস বা emulator সংযুক্ত নেই। Install/update, বাটনে বাস্তব ট্যাপ, seamless playback, orientation, cutout, accessibility ও PiP ফেরার device test করা হয়নি। অফলাইন lint dependency সীমাবদ্ধতার কারণে lintVital tasks বাদ দিয়ে build করা হয়েছে; lint পাস দাবি করা হচ্ছে না। + +## ফোনে পরীক্ষা + +1. ভিডিও চালিয়ে বাঁ দিকের নিচমুখী বাটন চাপুন: আগের স্ক্রিন ও মিনি প্লেয়ার আসবে; একই ভিডিও ও কিউ চলবে। মিনি প্লেয়ার খুলে অবস্থান মিলিয়ে দেখুন। +2. Pause করে এবং buffering-এর সময় বাটন চাপুন: paused/play intent অপরিবর্তিত থাকবে। +3. Controls auto-hide হওয়ার পরেও বাটন পরীক্ষা করুন। Fullscreen, rotation ও notch/cutout-এ দৃশ্যমানতা ও touch target দেখুন। +4. PiP-এ বাটন থাকবে না; PiP থেকে ফিরে বাটন আসবে। অডিও/ভিডিও switch, notification Play/Pause, seek, queue ও downloads-এর আগের আচরণও দেখুন। + +## সোর্স থেকে বিল্ড + +JDK 21 ও Android SDK দিয়ে নিজের local.properties তৈরি করে চালান: + +```sh +./gradlew :app:assembleCompactTest :app:testCompactTestUnitTest +python3 tools/verify-current-update.py +``` + +এই পরিবেশে `--offline --rerun-tasks -x :app:lintVitalAnalyzeCompactTest -x :app:lintVitalReportCompactTest -x :app:lintVitalCompactTest` দিয়ে Gradle চালানো হয়েছে। ZIP-এ editable source, Gradle wrapper, vendored libraries, JVM tests ও বর্তমান verification evidence আছে; machine-local SDK path, build caches, APK ও signing key নেই। নিজের সোর্স বিল্ডের signing key আলাদা হতে পারে। ঐতিহাসিক source-scope check-এর জন্য আগের notification-audio source ZIP প্রয়োজন, APK build বা JVM tests-এর জন্য নয়। diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0940fb1..f9de217 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,8 @@ android { applicationId = "com.sparktube.app" minSdk = 24 targetSdk = 34 - versionCode = 10 - versionName = "1.4.1" + versionCode = 11 + versionName = "1.4.2" } // Release signing: credentials come from environment variables so they @@ -51,6 +51,20 @@ android { signingConfig = signingConfigs.getByName("release") } } + // Compact test APK: use D8's non-debug dex packaging, but keep every + // class/resource and the existing test-build behavior. No R8 shrinking, + // obfuscation, language filtering, ABI filtering or feature removal. + create("compactTest") { + initWith(getByName("debug")) + isDebuggable = false + isMinifyEnabled = false + isShrinkResources = false + signingConfig = signingConfigs.getByName("debug") + matchingFallbacks += listOf("debug") + // Keep the debug test's update-check/crash-report behavior even + // though this compact APK does not allow debugger attachment. + buildConfigField("boolean", "DEBUG", "true") + } } compileOptions { @@ -80,6 +94,7 @@ kotlin { dependencies { val media3Version = "1.3.1" + testImplementation(files("libs/test/junit-4.13.2.jar", "libs/test/hamcrest-core-1.3.jar")) implementation("androidx.core:core-ktx:1.13.1") implementation("androidx.appcompat:appcompat:1.7.0") implementation("com.google.android.material:material:1.12.0") diff --git a/app/libs/test/hamcrest-core-1.3.jar b/app/libs/test/hamcrest-core-1.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..9d5fe16e3dd37ebe79a36f61f5d0e1a69a653a8a GIT binary patch literal 45024 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 UgpPtD2z>DYYp}SJr5?)v0beu;=l}o! literal 0 HcmV?d00001 diff --git a/app/libs/test/junit-4.13.2.jar b/app/libs/test/junit-4.13.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..6da55d8b8520dcc03c250a605151cc0d23a45518 GIT binary patch literal 384581 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%0A|RLyRTZ?5*b0Kn~9_w90>zXi(v|D#%u(jz#g2 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 oKveFPto?=7N*A1c2%g| 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

`)ZZo?PM~)f*OI;Nlt*N`a@~;G*&9Se} zx7IYsjdZR<6_gi5kW2KVp}0^P9*rSaDB1##h|&0#r34|giGc9hbX|^muucL7! zo~5+-$|pH`Kl`6XgXw z{Z4oG&s;&`N_onhVy(rgy&PvqMRqE~TAbOq$XuB{jB#FBS$&RD9*HcaX$@M;r51X^ z&RI%ZYjhXgB5qlvl9@%Z3i+gC)o}?dm%e{+K5oGJJPO~xe6jz>41>Snd>sCl`}!}y z_ixYW&*i3`PdUhn$V|V|%>0Ws`C>R4@|EyaAsLu*Zc;NbOr>DvIQhn;{tB*G@Tel4cs0x*o{#-Z0yi`T{B3+-uIs z{dWnGDy3L3q=!|#oCGj|2N5CeF2x6T(J)4WOi7D2y+vA?Q3_`Ch_Vg%PuX>8@7d;0 zVt-Iu_rP5XpL9=Y9I*~uKR{Fe7tj|CNEL1zfPc(zK`=_xkX0N7Quy)@=L=Z>rj|Gj z-j}YsoC#ev^)@Bz3E0A8m~fIND=R|V9-(0o4O7m+nS->&uL1du+R^1zo(sp}E<&g{ zg3boewC$|9Bmv@ff`LQ{=*B%9>da=tO!#yIQ(5Nk;2twIV41n6f%h1-!-=jn*Q5y5 z_R-R@YDl;-xn=`DgVc?3O=ARo3hX4xs|ts0^>D?ao>VGc#SVDhX2c?y;!>hHQESA? z`FXb)GE#)=MP6V3wYGTQeGUA_a#P(WjQ{)PjK6Vq`u7(VxB1Ky{={D%`hU2<3p+Uc z8-@KxahXhADO{5Pkw-u<9bUJ|?H2?0>)VW(L2c~kR(LHGdA*T~Y9H%IEKwRpTKg~KPvfVr?>BF#KZv=+fAiCa5=oG+{$4{;hXii{r%D@*ySgB@%hsPo z)wmW>Hq-Suo|^{+_Uxx?KPTtiq$s%-A=@_dj=;ZiQM8nO38cckbX|o8Ua}jRhD}0` zLsnPi0&CPuxe%{zcm-8;cJ!Zu!O)UAQQPv8=*4Y@j-^mr(LF4a? z^X#8Cu?zY3pl}G8<%?MwhI*XlMiIFk3Y4!wC&tl?AJtHD#djLJn4Al-n_D8Gi!ha@ zprZ%xlCh?sJ?emA=oZrtCW7de(oe|65=-?40?fgQxb`ZCC<<8pQZ(ieslWAco{pc! z8i0KkWh`y!BlX6y%8F=^!s!@5>jEnj`98RQWB^!?6O1PGrxe@ziZ(ruDbim;_z`!< z7!oUuFoaV)Xt_+-P#h**V{k-CK)Nd&n}0`ehDQk)>E+=szdAL)COI>dlIQN>5q3$t zS0S1ybw$n|H5+7|n_r%b>zig3N)O3g6l8~F3?+wxFVG}gB2OS%J`plZ%eI+GjIAlI z>syrVb7<`$zYxJ1R;G`>b=cf7Ap0PrBD%tKN~yCn|`lNbfLE zP=Vw^WI2T;)bd}!2$8LkrD*w(Fa+>{!YY&S+2iz~8LNK&@J{Uf(REhVINSLA#k-`+ z#}q);q!rh=pAp~D<-PHe(e?54$y3KKnv1gXsE(kL-)`DKJkV7dk_4fwUhu)_mhF|h zctzUInyXMdVyB4e3;<()UXi^55Y4qEJDS)a*peJ!U*YIbv{aw#qmaKbz#T3&XGob{ zh|@xV^ycwds?`UKO8dq7A6%rg#aadzodKp@sRwQeiE&aS87kc8lqbrT9nu6CS@hZc zuB>_#!U<2lE?1}~e$@t9N$fMT)qFO#T){7F+f=49UAwlv`v@r^-}u&#(HNUrU^}*J zH%8$|?*Cx50AZXs#j`D1*%PE~xxtV>=`tF2phB>bY-5%bUopyCJ!qPH_ETvt9S?hC zjXrwjj_>)!C8)J3yTbDQXN6GiK{5iC@G@FvypZ#rQR))m4~c5pfI)pfP+7o!KHW8P z&Ba^A+iOMEPfgR66n2!w>-(yTz!~z4`TgrFZ2Bd z+zd8Jt&VD-tnvx$Ds#K*%QxKRSlb6Wok#1S1VE~3aC<0m%Rq^;AZ{}z>98sRga~3E ztRoqbt_8{x#K<@{a>ab4LzpgVlnVnoBmMPmPowjDt9NbJIlZm5^%k8CWzI=_ES%G3 zJF-k(f_5H>nyDg4jw1eMo)acaKTp|joWja&gG;DOg!Sh*^>RUFKc@kzOAJXU5J5rH zucr?7RZho53sw?SG6 zKeI!h`rmoyp`S`@RLeESG@B4t+ETcSLf*EqiH39slzFVUy~22e6gevQu{X!J4t5rL zZNK2gf8JZ#OoBV>%pCU+2DitQOp%=;HzR0nsg{&}n)kt#1_3s z_S6Mu??9E8pw#I~)2&P{J8&L|VTQqGJH(97m}x%3FZc===Vl$tv|_O{_%-zFI5W`D zn6xn+#qi|oIgU4C+sv&#qR${4h~vbOA3D=rKs*zUk9h0Zn_mUxaSjggKU@CZV7ef! z?imo*FESK819(?CJfENp;$8XOu||O-W2>vMhFT2KXE=_lx4}(}yWP?q4uJcz0H^E{ zn{k_{`{X4Op%A2oX9WFfj%kP5XSUX1thf*^IrzoC<~ou4*dx{W4YgFChs{hgPgbk= ziO993c+;Ph@=FxY=-DS{(fW^+^53ll|AmA8Oey|9QbZfozH4DFpnveju(81~TGe2u zyY+(N0}PIdnAiIWTT%)@cMyszC+q(6sq8!ssg>8{9-WM_h_{MCVON@_FG?6tU#t{qRy@p?%jw4&qiueee*$|t~K}EiY6`KM2_x%1G zY=Axr1)@tsBJrxre5J0dvK;A=5I_u3mD{_a{k1madF?(J8$h)Tj@co@SnnyEU3N zu}Hsrq88`W8Ng;g`A-9?F)JOC%T~H{{xuh`NwoSgH>%uCc)!myaC)p07SqL=m)G|a zaAsrA0%}XfXk3ZtV|hohX7S!_xSLX}#NfAamsNv_eY(l2(n+D(lqfYg+$3PE&_3&% z9QKm>R(J-}XsvkZR9o{=ZE&WEuZdD(cvnL)VG24v1lvf`hjc%r;DrmYrt`ll%r##4q)@ zm4_*42qyNbwoa|pZ|cr5jzMJ?V9>APunTE0y*D{Ed##~I7yhaWFlm~h!X^`InKv(S zhFAPvDBCOsO}j0gr-9q<+vPQQ;rD~Ou`E@Fbnf1KCZ#&Y4IWQ&Wgpmngau4CTe@c7 zrB1fL6Q0s1qFy(x*ku_}VKb#Lx)AwrbStXJKatXSFT>1yMBFZot;_|%OajyiiEOTc zUYDHu4E727=OA(%6!G`5lChvXbBwOYV|qs_bmz$?NxlOcI${YyeYCI1GKx6Yv(tS3 z+ofMhJ=jmNN+l2+vUF)sgU`(#aO^I|<;ZNc3NA<*S+g2Ip{HZM=F^;*SQ@T@S(MY8 zpd&qGpg|rDvWQ0dFzPF`$k-cZPN2oJSu)@1@y<001R5-sGdUS111ztkpjGoEd+O17 zwH`Bjlzj5GilsJ9BUWO77v$NhU{e7AG7)lN6UVn|wZ2uK@t%APAHO$<&n+I+$437F zrdQBt%SY5|0I^!MfYUArm69n2mW4Xx zn&*9eVW$5c4Ouassl;thlDcReacnZG=+lHhH9oCGktfkZotO4pxTL2zl1yy)Or#`M z>aaWlc%AD38ZFd7JxfbaUJx&^PC}ljsAWHfRIMjIGMPRbXd0-j?C0l3Z=9h>yqW2) zGd_pVN{@GOx!@j&+U@_i)R@RM{+-M#dAjz7@mt5b&w5zAs?4#-<pYYLIO-(HjDVqp>*DL%aKX8{q8O!Zr-EVOe} zioAmN5);i8u%xAY;j%c_8m7&8f(eUvsvxB3WU00c_ym z{K2=JFHz_Nt!9-2pycTof-iOH)8?KR3MeA7Bv3v(%wv;~>u|#&)HNqB*~~SkentMF zv4k*bNnQ%K%dIogx>*AQj7@_$YNDi=I3B)4XLx+<#K0XIyj1(xuN%R=J5-KXAy-ic}FM+?-KU(o7W)J6O zlB&nu65ck6%oWLQveDLj2B+xP$bU$`sR5`Dh2WbEFe8~)sRZ;$`O1r@qL;z_ns}O5O)V_BgN}ik;pAK}SJ}Oo37=7qsTH_z=pRmBc7PSg zc}R5AJs|wtAp6OVB&lr!lW7W%Z4sr-@kYcbsl@762UNri)7Zuyblt&ZR?J<-90bEJ6SFqtDT;72|Ud3*>kGqUlN+dt&>3-dEE5lu>G^c(i0Q*W@qnj@c; zVo)Zw<3zs*Lys`s#EXT|TQszfKr)cW4D}ddA4OiAbAY+|cFu<_Z2^KKq%GT$IGgT! z-JKslx(`P%$uG2!2XsQ*Ll;{{8XonlKr!(fdti@3h=^>6h$`4`SRmDqHt;kNw4pb$K5myqucgpgpDqYs&y?k&%4OVA6t(%S z8yFH8GJv{rCnqy$rnf7QO~s(_bsiB{G|xrDo+B~l8%CijagYx)@tJ|dvWLV9ae`Z9 zf*Za>3|Qfyh(!3BLa>7Wj2#x~V6r3`f7A}g(Ur7Lh;`gMvP1vxeg>ya_T4#yZmr0J zZTAA}z(X$F1w4O{jJroB}3Jm1U2V>XT{V0bFJTP1nha4aJEFD$bzV%e;KkYrTZJmSU=HvTAM zz8wD2hWzpc0qdWxK9v7=O2ogfK7UWHe{kD>sUH>pUW17t#L)yZJ%1R@W#jvb)z-^{ z%7D-q1){)}+%ppbL;Itrinrdrm(WaVxdFM?D0Trf%HGaQZofW%tJ~m0=k*khq8#U{ znP_))$!L2RJ@N4PKJA{ybRnr_V7ssH+!l$F>U%X42#>7bu9 zWwp?0*r*ip;wT+5apgy;b^Itgu<#8-=O~e>5*Hm?1^9)wya#3Mr%cL1RVLoKvlqj` zV!MBO?7;*HDpWZl;hXo#vU{~-V$~;x6cYo=f(;4#yAa~kl{UHN+6g8NkQ|P=*lx1N zi}yH)pyT+bthkZm>s%I66`2g@uC$AC z&nE%f)7`?-K}ZQg>LJvBu`q#X{W*4L`KWuk^h^qsz)0K)oq|R0DV!rUtppq3NdmbYpM1 zXWakYLdQcu1%?=VZQV9!%KX8cwy!0v&2+-4y%2L36&FqGu5{Vhh{k4r#pWl5l;mQU zcS4?M?@!IQbS_H`4Dror%=6ltluHbf>v*N`33V2JnySNaX2;OUtX^D*7dw?f z1gyWefo&66{dd@~5JL`UEOj5^Ax|WrIcK97sz4Tc2XyO{;^C{TtRskWdKjugh1gsX zoj}+iiE(ya8EYTXy4h@~&q~Gh<;mSpTF$DRo_T$0&sOqzca~z>Z-G4nqoR z5`Guk!Rz5#sJnUWl2jBR2!pn8esMKh+pcu&o|mp%d_QgDdxid@8z?MfjT)6NAL}Nv zOj;(Q3Ug1y)|9?GBgIjHZzjbN5Pzt`M>y5uZFbefh8`g#;Iw3eOAY*BMAqzP&e5w!JrTvAs7*KduWWzfHXSZdk8Hp|jU-n3Q#x zybYd-fmoIrQgr7KkqKW#*bd>Clj}+)zQDaQ6H>%kW$GtWnMM@fNmW6nhAe*t zMEkM=(;n5PpJOzlekschfY1Zh20lV3R^{bJ;0+t>-4*pzMcfLyAoIu45Yi|<06k_B zzit2J^GP8aA9KNY=hbVL3VHX+w`1JUaJqSb{4L9QV&qiU&2lPs+VX$}rkRJJUuVHl zRy-Ip+V5^B=R+P^gSPV~q(BQyzWVjsHN?dcB_M#*E!`9B$u{W()Y`wu3SP+)f`xUH zgs#}TW!z%+`})uqyBzT@nct~;(pLU<>B$&n@>d>#EqK-wlktKSFOBSF({#xBdaR{k z+4}1sj;ZNi`LBQEWq*C)@BUvVuO>Gm z4foDmn;RxGjE#)s_uJU37N3QuHMT_2$x}yg<2xR(4sUlZ#3j@#Mf-NBXAZ;jd4%;U zA|z7ph^_13k_~gqhP%tpgO`X2t*=Ozsy$2`!q@b}la-YUl&!EA4!pjz@wR5qFGNaCDX z{%$&v##&KvKP4HKSVC>@hfO(zqH5yXEWQyOY|J~-6d6*I5|1ukn26`OIj4A&JS-ci zns>UlC;KCGK9EP)tkQm!WxFm6*$`h4b^O#g5a$n5ZUD8EVc7P(+ZnfL2d(;D6 zocCDw4qU^75_fApX~Y})EMSGhN?|p?d zi`q_&?4F%&YhOjEp1jrxy9X0KVJY^OuPb414tub^=ZV;#pyHV`j8*^71>2s+Pu2|(E98e=bC-{l} zWR%6BnvGDz_{9#B?E}4<$&1v1X_xBPef@plB+%Z0Kr#(klJm7sIp=C2#MW;OJR?_O zJ2B0ju16=FLpDxl8)&(0mSOzP9dxWPv#%zgvDpJsfhJD9lp*m^K$eX5?bH|%NiyKA zx`|*iK+LK+f{L8gI$`HpruJmumoF`v6ULxWb!Xm}2F)!Uv;m2D^mt(+_dki^%za&N ze`WD5d}6Hs8IAi7$;|(r%l@~rm9;bHF|Y+#|I1o5O+n+6S&!x}iBOGa%J(!Is7eNo z_qE zAxn^}Z`8fPgv20HB&Zn&h<_BYir4-H5N=Gp_!fp+5kV| zdJpnbQ}L#1Dpw3-x!6b;>$;F44|O=PYvyLYw;B-q%Hk!6^L2PjX<7WIPta<$8K$`i z1*@2U-k4*vArkZoCTgH_bBpO@8&gMd#YuAB%Ed zM+a!kX@)s}#1FJ5Z4Q-^@BWDLcs}(!`C3y(uoXE*BOHs=V)=A+z6>K=7MVw+MXsLY ztqaceV9Pb7@AvpL6ypttP05DCsPaM?{S1_^oGc!x9oA)!W ze8{+X722YGjOHVQ5lb6nRz;1TQNN#i)=C~h9FRr}6Z;BY-LN(CgMW@f8BK@?d!2s} zVFy?{a_WbvEa5+;Vib0)&F`pw1qjecyiC9>-TzFI3FCSV_q`*c&L>a^sDW;_MA$8= z-b)e~89d7GbI9*U_9AYRr&p&@BXv%}vOGS_`2jJ7FiF-TQ=1yyWUhSMst(qTYA21Y zl$8Q&fthheD|H7G7-(yNnsvKL-F=j$ z!EgWSdhmF*8v|QAS%Vx_8lxS?tmx#kYzrp^a`06v`p%DLXc$i`TDki$t90H}qZYK# zxriVOSR60Tkl?O^TcF2K3T~_bm!KBRPskCM&a0@)jSIjmQi zFedbr0KWV6p4-E0=cH&<+tMxT(nJrYuCdC9UNz`6Mi!1OKQ~5{h&5iMzqreT7}I_H z)pH-46m56oNFbF5dTj7ooew^5*exW@;OQprfEi~K?@sM;z5{GV>#iU+*8%lyf=-R) z=8k^HDbzY&iA?L@wtCehJS8oE0>dqMHsMe%f5L5eW706B-fS7?@;$O%sFpMDmE!pd zQ}$i3V>?{1$;*A6pmg5c<~9%ZW&Vc(zX-{a1*8YED1@-)4%xCx|)~2uJk= zh4%;pK?_D_)zUE>_G-YAwIIPz^=YqtYhOJjiELeN{3$;~EyqyBb%+pyZv3KoFfh@i zAC(_anHSJkT~P?qtoT##0%K!fBuKMv$))9L2b#Q6m=C8L1IB2TNCz7mcK zDcsZhh~M6o(O-kSxaZy{pkJH3I>56)J&ZSJnlCD1$FxokEude`z|9_8KEW#VwD(f4 z3G~MoIQy->AMZRb9~})svh#s%m3RuvXRBN0$WUGHmshDR&*$ zzw=vX-%N?GiZ~E63{%eiOky<|V7uW51MRA1FwP(j(-!;4nb%V^BtGk98$i6e5~mkQ z9*2+c?Da<=q0f!u;*p??V)dYp!jd%XpNL2mTb@^;<`A#0!YX$mC<7%;bzGK2(8rM^ z2P2$wnzq*EyNaUxI7`>&nBI^W^e%QMF^4rE9yr|%{LLfnJcu_z;LT1@0#{pc2mSSd zq`zzto#KcTDk6g0Z9L2ULK8}In?h{Nq_taa!V@usY+@3^h1@v8CM_5uW4PDuEi}g3 z@v_R1qbp4kU$Wc3bYD-=HQUA=wkbl$T+FRRI`nw&(rlU3h})o#o)Ga7f0&l_{AbB# zJJXfs_USQ%F@>FGWnE)J`e-hL$TFi{dhVX_0_KGnF}&>WFo@f zs^!Gg`hA63!jWlQh4E^evI+Ut=5-!Ki2=dZ9K7jS=^;D!u?H9@Q2bjOn6)&!=BkMf zCO^A;!7-o|1I&OdrxUJI6LMv$T-_qUK?sApf+W#=Jn#`CtSpL(e&=W!mw*+BP(ZzL3>q5xm3ZS=UEO zZJ7>er$7h)T@jO1UUT={$^Iw5bmjB$*aApBn=*2pAra(TpDm#LmJlzkqfF5RkuDW> zMQNV*cm;jzRu}RdXQWuxm7-Xnox5qDQd?eN38QK2?IuL>8cex1tBZ6cRMTSz*`rhc z5U24K)G}OENTG@iwqW||Ks$IJg9_D?X$BZr)Vnv~sY=}ehM2XWPy^_rJ(iin*az&y z!Ssf43@P@Ar6taFRfYJAD*aD5`Ls0&2wRh?7(mF>Z&O@EV^G%q%4t_1H6WK%RKy>x zC`D$p6x~FFw%vl z>*`vWmxZM;c;m?oDibnpcSnvX1*X7mKolXY^@?&va$q z+S4m%T@ZxZ7XiT*f71?sEbfqjty<|Oj-OvEIvdMOhyt$s#m!B}UWDx`$Q4lYv^X1U zMkFV3#6Od`w!2?LOtKye<1dk4D;7Y;o;MS#VS0(QPPABu)&I5KKD8*Z%syLd7anaI zFl@e*V|k%!RM7G!+T-5#3(ys^Re4v`MwEa~j@TKJ;KF9$!Akj|!IEShAjB0Ka64wII%DlHDF0 zk#f(b;Ot2;({L<9S&hC0=D|pF-!DT9 zj(TcpFI^aPhH`Z0-2c5#W)L6~Up+%Y=^|^9$bDTY2?|bCgkz*piRH#YvWDQ%MN#a@ zHz~4*R>_rL?dqxvd9LagGJb=J;WCS(G%_a;ED1JLvof5Uc)mbW4t*e2GZ;+sWJZyb zEsR{`C(tuLSoYK$Y-?>CGQl6B!zr0&hcvSkUG$`8C_Ty}wowXow`n$qTZ^z{MLpJH z-B__7o1QWaMpv@|)R~SF(z^33wm&|^l z92!6Ljv{MNB3Pqlf-k^qzuvryHV7*vTgxBaUo?zKuNDMukMc2O{WMI}?EJfUMI;Pq zzNM$hwY)AP?VR(3Jb5XT4VF@?c1cqC;jeO3uS#Dt1p==$<(}THK8njtC#D{;AJSv@CzEXG*m#5UkC?T zXUrD{sSnnpjcqoV8{&adZ;rB#u^DHvTc5&KZ%^?Fof)Gs>>8uf0_Bv7PScz~weoK6 zN#-#L%p=kK%CW60%gU@MQ(1BD% z$;SZ0%WRqZPBgn5BQ|_G`@ATx|@Av4omObO)2~!LK<5NFRG13H- z#?H}Cv$D^1S$tXytCX;rmgz#=R%rv?a=cQEaPIuxdUD<^wIA8ellr`lDlv&fAzK~& zr>Ebn(vn+vg0wQBZ5lCItY9_ue>~G#+zOG-3|uEW2l{)ExO={kifK|LXlT$LPpNsp zarZJ{pb@zz+HhIB!*O?GyN_3=-*DK)w*9VGzoX;aYzgw2DMxtIkG>fYP1u!7b@CJH zNPcLOT@Y5;pqTI>efSk?WPDQ87WK-j&W^kHGr+*gG4t(PIQV*r1K+oi`==*28nKjV z%Oe=9IlnhHHeyEzJFw77I-i}1oELP{aQ`rs)J4=>s$ooT7nQd4MVkcX#=_WP;u zdP8k+vn9H;2qYKuTCa&oDDIJvtDm!5H?t$Chf!L+T$&*}kMz%%$lu;8WO23h5X=Yo{O?-0Kw;&BCV=nq4c1YB<*fLPxIB1H3H3eNh14|3vrCgq2V-}FD zuMRkw#O~yB$)|=FTJkdp%d%vGlaGJ83T>+2EuFAB^)@VMUBGgMrG>E+MgCG_rS*Ba zkDpIq<_fdh`l2zSB;~hu(Q_NqUL*CqcWhFjE_BgaATal3JE!}tF!sy57#T+2uMm(a zVnCx1xmDoJKwf{`t9!PQ{-6`Cd%S^pm-8!@d;0ZnX2)a#4<5p~DVoNwB%XSY0_E|O z#`il3tk+Htm+0?R2A?7@t8t0|iG`_x_DePN$@?C&18LM01(j8F!x0PQOBp z)Ly*BQ^z*yoc0NaY8Wp~H2WFQ`fzM%JiC#)E+UOD4~Bq>(mZUslY86SSYR>Tkp^?dwEC7G}Vzf^(w@M)vNQ)X_6v2~dcVSQzI|IoRqL4y0dP3xE4x)?I_iQO! zdg2s!%P5($Yk3H2Jv-mrxU>KlO6w2O@A|%Oe%rX5S0<*cUtUxV&o^Z0wJWGdzPzFe zv+ELWIdHT(fWGjhIZ&H}(aokuSPH2hhQ&Yifs_$CSJ9Q0YE=;UlC>CC7dD4y`6G4* z6;M}O%w@OA-+jQnxksNx@|co9n9JB_(kwSR?JhPh7tk+1j{6rTYU57URR6iWLVmL0 z|MTsQ^?$tHikRD5m;nA(xBd$sjnPAd^lzSG9SXlRLO*$ifZ2nxoRuF#EGJ%Y$f~ahg+OqGMHfQE_NkjrAuPQo|qB}|4 zLbQVJRDLvQs;0X4wxfOMOpfYk{jIo`pafvgJCSC~_~Y#QHV!9QgRN->2?n5eS^jJC z^3w4wlg~vGJx!>vro$L=B@1RxWv=Ep;QbN;fs!z>+twS60m#NfldL-pWJDR>@%4iQ zsqmvDA*BNu=%!$_ES>2kr+=0Gfa0=FK1iK=aYVe_Kh_P&aT(1c71qP4;tKBg^#uPt z7_soF|FU@BMNSap!6g1n;C{UZ_fp;1-OFg5yPN-}3*K7_pqm%#XR24vpiZnaMY+#f z%^00Ej83YALogf%<}}KI;&`8kntJw7JjO?ig%942_ZkXyA<08Gk=og3Li7V2BK&KD zqc2v|=#FSn)AJvzYW-#Nv9Zs`#q1yDtN-(g5^%Hx=-K`shFWO~YBnqUXq+Z3QDwze z+pEjp3YU>8u}RF0#j@!9nI$~8i9Kh2IcDatvX^^PIuSO(a05B|b59WHk5R{zwbzWS-2_-NB=z8W3VXLh)HZLe1(K7zeI+Sv_Fh4+waOI*%J_@Sd9QR)O_HDd382POe^nGd)fBc@2QqlQN6+J zY5tJA`W;_m{8XQe>lJhvgiWii#kc~KccCgtV>$_ghNOf)PvOF}1Z_3}=$|2JdtW?t z*7U&s_CXq^8y_o0RUjbe<*4}8JIADlz5^4aOfYsU|LkY&;*pe!$>5L5<0!&`sp2e) z7CUMw7zwW}6V03>VPs8LlFSEnS?;#uy4Pg&I^>%nIdUp;k8kU|^PNuWw3hHS-_`{b zwXZ&iDz>12kvV274YaYfAjP4F6twpl%Uq~dmZI7_^MuSBtRbp{4A~wV57CBs)JS%*dd?(ua{^B7Xxh098L*P}sEdtz6XGM7TpoHrVf_XuF8Rzf| zTx$fHTDocfd(3g9?-@H?BBn=Qipty$lWNS+e&>bRU;zY|Y?%dS7*m-0=fBgo*GVr5 z?OWa}sdIMrDlWKR`*J><>m6T$&V)o=laBMhArmf9@C@eyznkd34&~AgF~zjx2TI25 zLdP*Ry#V<&4R)+h;>ek$#KUBB%OMf1OE^pDLI2!6I-uK26Y}yN*NIyMqJy+1G=t{W zffoq2532!rv2tI?iF?reqnhGMn%9r~EPBCz#A$f`$M1^&`YPqMGdBH9A^8`ms-R|p zD1`Lh-%y8&=!XJMa#kF73ff zA;XUZk$bIN#9h2S>TDJeBCLNyzhKjT;c7TO`ugyZ;_)RkCzgk%d}tqRNnNQZN!|)6 z23=f7wM!g>MV>TKfi|IE0-4-krZG=jQvlYSLRq?UFtP^w;3|73{;ktheaX71YN(SL z?bF=DhUC(?el!x?SD>eBy8dc*5_qH9ZU)|9vswMo*-sodvI~jt_i}a@jK8Cme%R}N zn#8zeu0BLN!feTE8Ja?hqBYR~<0(^5-7bf!_@i39&};+H$oeidpS=k9`S^UEyw36TzoX7j(^E;9Fp>^x3iZ1D@{F<|RwZ8@b3dcUE;X>ngwS;Tf z;gM(wfW%Uw-N+C161ZiAKAzH=HC_PsxrQ|yQ6-gMvulkqesaQ+8hU}5UqY+eOq1a> z#hfZK6>38Ky|)3Y%aYBR3G%M<21-E_E*uS6|5Qz?2_%>Bd##ru&`SG-@9%rhTNWE> zS{S_4Z{JnuQ%#Rv^-Z#UP6s{D>BQ}#l>OK1Q|FGES&p35&k8ls$6c%N4|OPMMk~r! zsTop9vCDFj@C*d><7x*{kw^~2t7Hk6SqudH$M2BVYXEp25zz((I zuJSGkjLBnyAS9}VQAPdNgZ8%g}psv=yGVXcsmEp<~e-+iU zfE8#Lh$4|7&~D4DX+n}wFVIvaIx3M4si-p0)!ov%gClnT_<O`uD6ZsebUJ1@Y%!W!^z#QiVFaBU>_bu($j<8Q#Zn>`uebEKf0vH0Ze(h$KZ>q0| zf8HETGC<~!I%MN%&Toz2_&X?{mQM$I%G><_TZH7iHcyh>j!<-LtRJOijqwgM(CoHB{c4ZSKx_1Ro3 znc<=h$QH=LI0|fPfcwJU!>UFN(|_+!!Q7NVoKVWSoqcawvN$r6gpAih8IJBVFhf^Y z0;eF&D=TF8%l2-UQ3fZW7V#L^Hr{sH5smgylm^Ijk_O1kl7_E+t7{wojuAcv(FC(E znMLo&vKK|J#LHD`zuUe@wsmI=Nc@lTs8X!uZT%Y=4HE1%UYHr;s~=aU&kWu# zM!KY-r#VSm1kJ*cXzrN+>a-=k$C(VRU8F^y)*YhbI}8-hW(Y}G^GISoTF^;%(Pg19 zXzfT2z%)#hhUd_H=SHqpAY%{z5toVOH4;g3V5oGF^d+Y1LuPByLJVh#a;K*f$+-*e zcS2qQjNH<@Q)mLs&+Os9R_d&RakS^p%5wTge<<>Qt}gk{_{=|4Y)GSRnztZ>W7}%~lE^nuuN| z^vyUuELT*>6;S7xrD(DOuk8Y=%aftdQ!wCXg!TiK{V;~8t1f$1CL0wWcFIbkVG z&<0a&2dcrlIQq#QyZOK-UFj9?Ey!Nu9*x?A!cslU3>r1tF<0kd%0`fNtp-*_&_lEv zCxU}~w@?Cv05qKl^qYD;xN5(cE2rf#tAbFf5qrCZM5x&BjDd=6$aHJmM$7TuYYHUf zCr4M>q+rmT4TlsmlPyhsp|$6%E=Z@Xjd{}Rm87|;@{{_X0+)m{3wQxUE*pD!boXe< zlk&4mOP9XE=*;0wq#J6Xl@m8k`Gm?{KJ56;0xqp|>$_e@B+iT&m@`5Xon_6W`iP}+ z6B-uk2q?((C+@MWsYI1Q>SLRGlW#VhjEzk^n0izJJ<;5X7uG=(B)D&{@#RqWTLHRE z%TY1GBGJ3S;d;vD-iN_@>P%<&Pt)W0!zfD715i;)y5>zV!Yzxd-I@19RC-}8-8nc3 z^K?3DpbKRCgI4kSO=7dG@V8e?n(S^+QXCzeE;VDLTq3bU_*|_8G+f{f{DR3iL`73LKfHJB|n$18G6^(4Hz45A1U%3v0r+I0oD8?(RZ=DI4SJ5miGB{cFb&w!QaCvpn^n zCFnv~7h4qnNr~|-sRk~6w{Vp{gG*6u@5XNJ4yz zTGPy<4TJoMY2ameuTdlf&B<+d?ot5}h*U?HR@1Irb8sZnUktUyQVTG*8Cg1=tf8<3 znv)}PM(Jf&8r^R$q_tFqZf)_eP#Mc|_7HoK^!>TIm_Zw?(t#zqxTn}K!UMPkSBwa8 z(9ObGHgIM52&T%75}Z+``chi5dvd6*;&(BYh4ii#3l1fF)nrc=-N97JB_{`xFSpAI ztDiSFN?_j;h0Akxm}h4P4#nM&^eqzYeVl^m!65d3wQ>ox)P(L(8YcYg@b-BPumZ2p z17W!gJvf8p9Lf%04@ex58{G&0&hQaHD;Q*mo+L9aWaayz)-GN|7oOeg5?*5npH+Tf z6`KkkLy^$_q$i^g4|M3M!ZAq=bVAs_r$Q*249e6YrIHU5v-{JamW7i}4_ z0|E3f*k?D2=iM$3FeGgSs4C_gkWtndeixt$)d{U%?rQ0nSB%wg@9uY?%Q(eOI{Ho1 zvm|EHJs?yoQ#8}?w0cX1I!i=EUo<7IM|Efl4u@CU0RI%YX%3QF0TepRSSqHzBP13C*FzLp=vkU&K`<`s(~s(XE;5FH;Ad7&X!hmf zkDH_7E#+6m&x6Jh+&>+_{~Pk@A5l94>;I-||6h)d|0}YXNfpZeVBSv+rZ3DzMjDRi z>PwaXG6I6IEbKuyup=qmTpopCBKrH~hfDi8>=$%|Z~btH+c`yM)C+mhzEk(PV%%Dk zahB)OCRAso2rN8k<+kOa6n0fH@4u;`fq_`+Hx;n)TW2*@sgN2nVs|=nMFhX#EirE2 zB!ovYyO#S>KfbFEts>@o=zKbT*I$;|8-XBaA)5P|FTT-QT}tOnuVel-yu8e9o|k@( zCcyuQ2mCkAIR8J5KjFU&kK*LbK9l0$-0xLgZd;eVK`7ciRkJLtL1laqLcfYn76&Q3 zw#L>_Hmygw;Jt%xYh8sv%=W_ePke^Ooz~OAo4p|8x>!K=~a zmMFz3eJ@L!TMC{AV)+~M@3W70qg`Z~C@`A-Q z4g6zAFco&aZn_#n3*VpU8nDipsW#qE7sDo<^#Z`w-R&w1Dtj`WLZD}S^z=c-%-UJH zTH5%63-8sCwi1F(+a^djW64!eB@zOnN}WyM43QuQmgUE;CT|wN5*m0~1=m7e{oiB< zKiKCUN>B(B_iN6Wv1s7TYk=pj7JKQ_6NR^ugGy*6a<684A=8hPnWFkI`0S+(jygoZ ztZTFA^v4@UX`N&yp!Z}7kF`lJV8J*@Sdnj#wH`PA$uFZ-fGb(C9nIgp2I+V~S(px6`h8i+(thdkql2EPIk8YkaU_8H|x;a#vAmrSz7>mEUuDs%ZL zzIrV>&>JUaa`Jd}LX^*#lXHfBIx^T9J(qt~6esVQqZc zBCT}UKIJlpsSRiJnN8P@LQS9B+O*~yp=XfCyA#Od&-uHjKjsFo-es`*wIs^CBRv^_ z!w+!!wMKx6?q5dgr{WwsiANbo-zIi%{{gycriJSrKBw}Df3(5*4}Qr1#sSSN^Z}+c z0y=s|f6eET7PiI)#{WK9#>JT_eV!}_K4SP4nO8vgqGge(6pgES6n|A_&#C&wGxw4w zqP%2^(MN7kwxqjNx+8OYRk%p{<)qK(ZQRG)%GI{js6@l*ny#-VHQ8KTjJL5n+*f#L zetgybmO_5r$QFu)lGm8O*(|zq0BK%`)-8dowHY4_(k{Nud`?*93m|Kky{uzYkNUPD zd{u@7AXCgM6t+_8a)3Oi!)n0RGKDy&j~xzgF*^mRzl33_G&{!_8%sWd8el#LvnQf& z?~g3Csn`!Du@2c9mDylFbEC&?rZ&3x#@9sqEAf3+HxAEgrO3wLR%Njj;ndvPVQsZm zIEIX!X{I>2Jn}?1=@PSBFY#m(@=x}m%$JEpqD%l~Qe8jW!)th`LXD;tvmbIJKL$mt z!)QUiPSSmS)w*Sx7QpvX*m%RXp{e7xv_I!~*Lj&f6@UA;_S1GyAAxni!t0bMcIvr? zEf`yZ2C1#pk`D971*{42; za^~Q!hXi07DCWaPA8HDbFcMKC?l5d}$wnj(PT^5mC798f@EmUdSg}_x8mc!1+PaC? zIgf0HQCvvvEQ{jR%us(QON^hJvxKa{;Ob5EkFboE#i%XCAig5&@p})0cN(&MKrS^_ z9sq_>_$xL82Gg-{QwWB8%Mhkx6W*-|Cqc63r)&0B)otomY&Xf^5!q3(sxL7*2Ak)Z z7SrGD9c%&?T>brU($<&SFHAGHKiq$?aaooy+&ys8Mgh>sMZz%3jx+|5g0bIZQPQOv zsO*&ar1AqXg77>BvvRuVh3cS1dv3{JlW;W`0%i;C8wYWIu@wsiR{4xBk4>Jk`v*P9 z*DLpdya8R`5(ME9SP+lVUt3SJLOVNJChxMAF%RNP>Zxvk&4|&!&lG6lzpEWO6WfYK zibsjZoPdu>Zc-X^nKw_de)VNQ_xEE!Cl$15HjzoB!Xkj7Re44XIuJg}YY;x&S%liX z+kkiR$%4Vy$#WxhVO_`Ogz}q?3R3@3ADV74tvW?wm41IX6_bY4=PFvlk7fhgQ;|tu zD%r*xz4grKwQX>RLIFN!XU|9NS-gn1})FIu4bfuPz9|KDcX=0GYYi_5;&Pq>=;L|IVeV0 zmWfctWLc2kx#Go2($>ayJJ#8n`e8@E`R7b8owN1+irlvhwfWc4j|Mo8kX>+Wc<_;D0v`S%87*C)8|fix%!uU5FC@+w zR%TYO%lz1f@BnRJIErF(X$pnvV19!87+T+;(Z*y}vJ6nnNab1=E=a3>eq%4>y4@F! zht&ZNj`fdkl>v2Vc>8+tmw4U0q4ZpW%Au8naPIW;yBVOvm?;H7T{8ChpvOT~&n_3$ zMH+lea)SBMrFqkGd!Y|tT|YW#HN1QXZOoRAqJ%M0ObivsH6~Qgny;gMi#VnZip=-& zDFbPU~>0*YOpzbj%VUWE7JL9=XK^VD%}IA2zvjqg9C(E zQSpQJjb*mca;c(At#YrVUwy9+J>alMJ8c4|+XDdJfx1V^DMD39q2>U`!YCR@)`QO) zOb$3h|HUatMH|j0)Bc#K8Pwct7cy;E?G~~r_4PYA4Qye*u~RhU2%mGhb$C-`6ofd+ zGwUOA(C73s^^eC~ZDA6oN45d!<_>R^ z87TKU+?^2!Cd8t&KLT%P?TQO+Bo*YOCO+PQ7kjOJ@hIi1l=~2}(nSh<%|#Q)Ov=OW zA#BCbR%(cEx*zi=dA{d7z-LvLg13_ZxF- zDYlZW@kXfjl`I9ha)P6ddR5egLSG_!uex~rYVWgZ3zX`?@Mo31VC2u;V+ufVZ zKdO(SvM-5=pAW(1KRyJ~|NIbG+nEAvXkB>-l+0PFu{)=l^~KSsjN#+Dbr^VcZx z*O&f{eEvNm*Hg90LLwo7xR&ILZfvT8f~bjj3TGP=!OP}s#M@H^EU;HQfIVZrZX14p zW?T|ofj#Dg)-1?oMlZ^Dtd5Rz-i)4}j-F3wZMc6S-4sEB&&@%T&w#GRu!bKM_*DDA z+3qqSF>*{^M&hZ{Q}$>8!1JdS{3rc2P@!`GJqR?d`{j}Bt_y@Cj!WfMHN$yX^JaR3 zYYbTaLBW;}r_Oygt9DiO1XvGw1?~gsD)Et+#12?ImDWq~Fm4+;j?FqvysE3Sc?GoJ zMdVnUuS>v@>xU(B#x>N4X^3d(`M;~dLCTAyq>R{4M5}lVUSF?+G8;^X@r#Wxv`I`Vs)DAG8;VLf#->V?W%tZVG9R~ChTb0*g z!3v1R?~-}QYD76g)~v*qY~pL6@9M%;<=tyE$l}JG&Q3*MgaJDQU=3mZeHv1-*0?U^ zEyLv%v(BRw8grF{U;K58JD4dRNpKxA43*Clyppw7?)4>1F^*63sd>gqUiHv;#d~C- zBPvHDKTI0y+2eMLj`94(WA^dzK+}BUHWj;#a1yi0N)^`-?pHqf$fG6N6c{)I()>YN zPal}P$GbH%i_2@MObI$|H12?tQZ)aTd9gQa@E%_7aA>|(Ma1wJO3K|LT|6K7L5>#;$w+04iQEViKoFBZPtY zMo$8c6@$x~J2oM>5j;G(haC;oAmtHKjz`wn9VR#*M%UPD zxINrnzGem%$nZx7=}L1}4;l{&(V_}~-|$V!OiIsQO7{@>TP{^XJH+87j?1I-PMkJt zqm`k>J03AT_^$4`5DOq6xAc#ZC-|#2_~1EgSfcf_K|6Z;3z?#iBF+f2^Hj zbfk;6uG2}!Nyk>lHaqOtwr$(#j$N^B+qP{d9ouH#T5Iin&OYPbv-TK!+~4)9>Z|vg z&(u4AuZ8B-@YudTZ2A_rz@2)}l&P5xFswJ|oK(HS!M>9)!Ch-eb4*Iv=X@vN+j`ZG zbswvKpHht?Shg?FXMID$ZQ;IlqQ3?1U8GbxJ!XB*{{Hy(HPSo2^8G8QMfIOrqRD`Q zGm=H(ASC~Z*DS5Pg>0OBZO%lEW4zKx&FDEXL z&cU07Z+PpG{*4@Sn>#Qo-#x?6PH1K7LGTE{nxi0nR?DMgiMKJJ6$t7ui7)ZhfnxeS z5oou^{s*f_Lq#!91I48Hw_^G?h6n$9nEF>S{cD8UkU`-`{V1f}_#;9Iy_G@a{HPZ8 z0SYPLC*z+H%2kkyGJH*&sSWl8Zqw~t(=L!N6yaAmrnDFMZi+RfoVZmfJG1Ge&mq@V z`Zwm+`=ug!5EI~yF(?K3yHdi)h#;%abMgTUbC7u7;Onq2%##hFFyO}QR5ATVlu`)m zO;o}A^k%C26{|Xl_t>@=N0_zhsn=;7f%bZHSPri6!GxXqP<0wNxUS0F9tS1#HROq; zd#NLMqKG^8Gl{I;Q+Z1N5}_sv?VQk5k*N$*(nJ#48q>{yc49(buyrX#Dcyta1IlGy zq`d`iS~=?X%ODIpn}-mZff1_kH8u6i6suH+8HbI|Igi$34_pbr9V>Ue)+(tj$eI!E zJtkN!FuqYq*Iu^z>x6!r0 zyLkR-c#}5I#C|D`g41e|@45RFw@;x7ZvL}oBRRD} zpPVVX%O?wwl+)ZKYV}1wptt=T=zgyiVJ&*6MX3@#Z!c<$cLz zrW#sY)fHx{nc5&o#yxpGwcqrvQP@v+h-|J8LIcKX=V2Pp8q`4_jrRtwhusWY(q9D$ zs+0nDrN$@si{WcCx~NK$;L?7-K|KR#fdOj5cF^~>LzbSoN(DmSv4=7y$FM z$WbYO=3I-y8!WJ4mSfW>*K}mryVY~fVHS=uK}$D>Yi1Lp-99h_kxiNp>B@K0VK|A!{1h;EPBPB`XadZJ%of1!t?k4e|_LE1{n?VqZ0a6=USTteZoA1fl#r}R%%slzw1 zuFo)xA<`u`9@8>@jw?YHG#ekp*7lr|h*W7B?V zv=Q5Dz4RCxRP3@!K?_<=wZ<_R*r~KcG^scWN$uQKWZ0;t`!-Ja5kk)+_4A1(f-s> z@f-(T%%w3WIcl(wws}rvqukPwrGT*=&ld^%+>{-nb6=CNvolmtoS%u&`0M?&`)nmw z;&5eOffD*6=+w&3wS!b@_Qi2~*!B+zLG3aS4+AA6{I?R~{!bsflx3HEsozD`#;B~BbaW3J;@n-(WG`=!GZ{~)x8`-=?2aFEbbQ{ zooRIrvq?1(FjBMFeyBNlLDSGWthdn~>ne_EE0t5BTDb1myejEOsBf%IS-M*MH6Hpb zV!n4_u=;j})$(bJab3j>xTXjl7MwI(VpjUqOCPUuBU?*&32A;o??cqafC;B`oH=tVv< zbhyR^qSd1hWe?>IF@sx>oI?zeHz$?mm(+j^NYkKXN>ud_iA8`{sh>VD7E+cW8<9vS zPAZ1+A)i^b&2-^YCpU`B5&89_4IZ$bvd6%H>l|THzt9Vw*EzoCm-Cv*(<%Q4SCxuyVkzWA;pc z6BlHO<#`u-ulWb3P70R z!4n`O>nqLPL07H)3PxoUTAWRh`ZFxBz&0*FGBOU!l8OHvBFpO@$LktgtMm8t_XuuIw9t4X`#;Ro$vKK(xE*=ChvWOw%K9BzNb+g=VH9dWZ59laUgxId7n!v0rygF?Wp?xE1)C8bv>*`sVG0F zn&)D*(74$-v_#(aI@LaS6*Q>@Ls~LkddfcPjaM*{uywr~DNn#b@U5X*StHJPyxAmy z&v|_)a(U$~w#5oN(OeY{ZJvFeM<2^w{ zZ{>1uW+Y>}H9Kg+mY<@lq?gEW=}H5*g%VX!9u5yDU#HaBV%RS~<;SqtbY&CFT3-5R8h$8M)6s3ZEx5&Hn^;P-98 zcXG=P&Vz8t_B}?ah6JHiKP*Ajxi=@zH6h85!!!vjbZ*L@rD+!vkO7rWby_BvVh3pr zasxc;1|F@wRO@TytbGqg7UAI)MI%B>e9!3VwQdy6?N>($DFu**cn3GZ=!{H@3{Gj` zznX*M>SB*eTv!G%c>*}`fFpl|eNZC~Me6hua3{yT}x)1uZwSbTX|8Xcdy zUEl;Vk202pDURP_?etF`^O?(z+yZ@f2@pH8u#PXag!l035idrf@u-a0@4GRCuF(Xa z3I&RAE_}?stxEq+#y;=Sksua*tiNmkz9 z&rBUsskf%tuLD_+P5oYZ1jnZLDWh5T@O!qvy6lD+^E!1>!b1V~VNr}iZAF0<$q^i~ z6OljCzKhnjDC75cMEP*yU_2HHg{V!6bFD8)L^D}`yme=Pnw=N}%lHR>YhM5Vh3ft= zi2HA<16Cd65qX;anJkq;6cBVb!a&(9{6TdjHTfc2Hfz%A>ldkQn$ugq!lyo9bx167 zXZkUI#M@s0b0smWQu;^3hw&8VI%eQ3EYKou@9M8iX^a$@DWyP}OR*N8^|iR`$Qsvg@);8kJ!Va z;kWxkFxy%<$qptt6x5BN;$+LjH&+zX?1TIdObEm|JN6(_8fK&4*p>S-r0z7HAtH4$ zebE$Y(6W(z=|=5EJt2fquh?d>S#L$+c`iIu&nE6ekC-d#v zY=j+HiLS|L9ZvMX+Qjp|Tu?4OO;I7I*^yD7!dIONrU+8l3}cr3pU*mf5}nU~6Pbt7$oQ~qMO6E1pNomF#?J1@eiU)0S>l6Mojqd1BuS0{t{$c7D#lRRY0O6 z{1>82+oS~&-4KxIkWm6)iswlFh3N7s8|D9-=t>iIn9hAw{vf)cw4aU9ynhm1#g%wO z>7PVL{9i-|yFL78`@CYkx}pEy5}odU8YcfWDUz|Z`Ab2v4*XV*w*MHa`DcB+L1oPW zM;MVOZ>*ALEYxs_aJv-|QZjM3!9bKGmLWzO7nvk2OtBDD*3eu6zizd_M3vDCOgGvK z#FrgZSSmYA6#t1L+f+3Xl}lMZbaYJ1Lq<01BJ*Mccz@_NkvLa zIxhs_&W<+llrml_ehyR67?;3cp8|JLm&f-uyvWIQ z7wsGY->(()mYEhSuXs$hlI)11`+y2g?A^IDsI5hhnGLS>7-@Vr3kpr+pm~2W|Fgw9 zutWe{{fO+)rd&Ji7w&syC(D+`DFVB-ZJV(#^rtYKzD{SUcmB12m=*NSDNTL3m#6dZ z;!VPdkuGi+f;MJ#aOM}SnJxp4Sd;15{7o?rV^@%@(aYcUBU#63b}2 zrT(L&+#CuYbllmpxyE2m?JsM`<Dlc1@zf|;gA73Ab}u~ck&t8M0~-MIwEZuey z>PYPs#khs-=`x^1#e`tQJr^t+@r`PE*5P#%B6z8l4g$2hbWMs)%dlI)+aYO+0$0I`T}=WE}MV zoRIY+@@T=CIb|7YMKY!b9JN4A6DRc>B!^&Pr6Qe z&Anfrjv>G=FuC8(_g`u7Sny9rr1#bavr^oE{ z!bNmW1*-?I>n<%8E0%PR?{xtvW+fU40+EEBA`5VF$|e0Kn94S{t&&o$C#=In)%Yr5W%+gMTkZu;LErAp_;`F1ifz_oXb1e|6vy6k zOfl}?s)GDQtvGdxaSQ}SdQ4v+>JSsA-w zCh0%ICxRMESy6f}GDB z44n^+7InfHiIpA9lw5$1)EY3>F^66m9fetml*wbl)a>JM0%6bY`Ed|Z=W(?7XZeD& z_$F5t80?OKzJLFFPAKr72D|^c>8kR_-xpX-Z?YakA64$hp|UVfpoM{I{OT$k&MpPt zNqY_>nW$a6v5)LME%ZAQAyzCT_6h7wevo-?7JU@V-2EtJh569yFzt5Y?BTL&>jwxN z5sC=Ppnws+ecTQiUQmQRG>ZObhRuMXh#Yhn%Ao1kPK#22r6pV+`O;h{l`Ob;3YDxPN&um?*<@-0Jwx@Ql7l4cUHgd| z+qYa{kfj-T(magx_<(Og!#GRRwW9iPH-6?8jT9yF3J*dGoQ5l9qpg-FfZQ~INm^;n zGTjvAw3!)rAQ+tm4t1(_&^eci5F&iOb8oWVQOH7lF5lR%w3?gw;KT?U3wkZUgNioe z@LXbzLurh%bp~HjIuvG88T=Nrc;6%Hhu%uK z=!?|g3(h{~%3?+X)p_UOq+mvnd0-<|yvdY5lK+puyxW7NA-aJoUAiJP81K9hlD<<; zF6a7@55>wVQ*!kZrol2PR*k?)J4I+ah7ly)41~)Q*KTewOs0mW>P`v&oqSIh2=Q_c zq~b`)M9af^_l&LP3&mUr4oxhK8R9%VJSL49GGafI3(wwZot49Q>#{blI^@hzwQlC1 z9FGwDy+gQTkA{HPc2`YQj1;})xBxLyar0F zo{UUqBz2`zI*WAb?Kh6#8mHqRMlB2XW%cld;$=&{hN2}-WWRO{-pIK_b)h`2 zDj|-Tolhmvw#2X~{J27(uYCF?qY=!u)=@Zx%u-%ZD!ak^dz+|aa)^{zLOx$4f}mnQ zr)F_H%aCa*kNB)plOlxV$Bk@Wno7vbZk4VYO{r7)$Kz6wPASwUi4LJa>UkEKJfahi zzQZEQ)TtpvrxFW91QR);Vg$VCI4Q)ng7VLP4Y14~_Qno<_;Y3}p+EIe9vE|wf!7}X zcL8($hXVdbyTZ&6n&3t8cWB|Ix2cQkvbM(h1j4xIZRggb z`{5&(?eiYrr4L9pd={12P}B1@p3iz!HW&g$@sMlM%&GslcT~$I^CQ@S4{+>|V<3tE z@){BU%MJ_v_D~)ZzG1|WF*lMBZD#y+d_tA9&7b2-Pmp3Fm!Gs*w`_elAj!g_WsIDHN$CV<} zdX7e8J9)VW09ap-0|73W){>kvjFq zE*kBbQR#Ca5pN%<2rv_?grOLsusU<~Mpxd$*%bwxXyrHXf`N79K?Zrel~H5FP-_sy z?5V{QT;Cwhe`FEn)(a#6O^vqSyZY%%{r zY`OjO53@EKb@4HD@7|;S3XSBpxX1k4CgT#I#d^!^(qV&KqpoOC`JTN98cz+mtLfN+ znn8dsQEgW;pH}>dlfE`Haw|G}v}TroaAobru8V@e)S-QBvE5vzsiEXUM4U6)H?xqU z%f*O|@kkFt$Kv_Ay47o5^5>&4!f}UF`6XJOTf)|DBGrU1i(f5Q=+i8gFPbL2;VrJ4 zfyEB%HBHUq^$fH7P?!3bEteZjW!3e&%BCBW$sDFwDRujfDZT0iaOB#^+bSY(&A@>u z!#B)9Zz{SSJhb;_@d48DQq1n`u(4!C#sGh&%3?Q+TdK{z_J~Jiub&9UUimw~gJEG? zznYvr5|bg(AOI4}njV^^91&5+gQ20F>?t@*Pd_u zg9OT6eta|2F>UmkMrh*e#QMb)MHpGe%e#DKPp}_U`ak5C14VLXV`k%2e#naj4ho_D z=-X1h%T^ueNFNm>;sb3iE5}#aZ|@*5ba``mTeqy(g-$@j!0dx0+2?Vs10u z1mT1n_c|lj3mkc;pmRkRugUh~(Mj3u>3TrL3_EPUD7zW70h%MlVSf_Fc_OgYQWy~O zC0Jk7I_-znO!J&6Y0+LJmnEC4jmi z!6KC*R>zvT-yVYs&^(Z{hfa{%mn=4DJwuL%6}VUNs1yL_u@(6VceSh}OSTx}%UDD9 z^9pVIi=*xUArtY;o&!!rO^d8NKq8lC+f~jPVu4nO+7v0Bf7)=Nr$V(_tM}r&;4hd` z(sX_>M`{wUfYc4N?VvE)B>RbR)87VFqk*aodkDje8tVYcS;h^d>7L99+!zqJlOcV|5ua9ty9v6m zuTrUS$?e$lkm&i)(z6^><_>!+Z+ldJeeR3B8Sxu6mQ?)k;5Rmh9xd)=x63Mev#mnn z&FpJ+FXt1Atky@@O&2Au!vSv#U77H+Hh9o5Fty(h*5V63a1T&JpWImKJxjcstoY0q zZY^1jsNcw1u_S^XprcQ`ma-AE*?ucf=)ZAV5M#5$kkN$a2H{CTyM;RPjIRA98GA?ajmjOOgZrEN6& z*ZtBBe0`8gU`J9COZ7{|2fq8uj4ZR0Q`ScfZHn|3fPy4FnUyw36Z5W!~^D! z)}{f*_HB|TP1#*N7+LQa+}nT5b9zRQ@nTL{N%{&HD0-)Sb9~?eQvUe2x-Fj{A250l z2)Q9&v4pEM6vQltg&5F;q2|09q8AwQwkw0wgX}izrEOCPlf@D-1RG`TC5 z8Uxn%G||n2U}aJ?N$I4WZKwn{8;_ab&;pyzmBO5tG?5Up78Z(@QP$8M7IrGBp>CRg z4VTs^7O4f9dkU9y*}7!y=J>jfDN`2YuI8u6k$3k?-1 zC;JJj1SCWikao@)u|#B(?$0me&6zWphKmwMro24YyjaOm|v!r-a57Uvq?E|z8;!uz!*cy9T1Wnh9U@5IkoLXsbxC#_^u-lw-@iKky5pF3oS;SviV@1` z_z(h7_xSKMC(FHKha$>u1<1TRF_{(F^UIzf`hp>YVrH|RQFj?eFRFn7?W(@RFA7H> zN?>)7YlS>q--&+a(=$4O6`-F@+0GOU;Nyq=_3TR^=Z>3=_w)&#&mODGYpNv3d9E&2 z#C{}-4ZtV>?q!avI{ZL2(t0VFQ`sQ5q*;t}r~Js=j0IW1F(YLk`tY4u*u^I-5B__& z7=q0%z?SWJ7X|ad|8og|5-!Fm+yyv|)-~5+w}c>e&HHe38{*PrI^^`+=+ z?gX5db~3Xy`irv@l(k(^luMgEbjhN;Ac-g${ikuoRrJWkQ?m4w_nBFnyVN3*!e!b3hhc z`T!{y@e_BXIbIUH*;8+K($i=+n%087^pG?z$N0UR*HDy%vP=9 zz-t6a=uFsf*L#a*tclRByMX#yjoh9t%JdH>?gB57p9k%WR#LxrmkP;Hj4`c0u!V!Z zw^1c@A7`F{5{oLn)&6FixN7^=^2U8Vc%EhHz~{uxK5Z}hRnh*W5>JFzTf}pZlAz#9 zio)M+(L1YV0Z+ z-&^MM#tIp7I~QqKPATrtUY5+F3+{TcTa0d;B!Ftagl?gZDl9boX}+IAW%=H(1K_E~dnS>Ii%d>4tFP(1JZ_79f6e#18kyX&q<8m@Qx?luIo zPA^uv#rI{Ge9i^yEzec#?Q1Is&v_veu}ka5w2cnyg_0 zwHRW?j%P1Ipq-IOVqE;B7hisytt71HEr+H%i)a)e-$(CxP~PU$JU$kbog`%!FXi#j zI7tQTjFH%}jUcdpPJS^#TBv_K_yu#f9@$R~f?)}^q`D73=b;U7h&pr#fnf;g!ez9}y~ti0g;ahLp+KQ3n{`H)iiPnC_k~%iL&)Qul<5?b z;YK>$tQXfKN>@6ayZhIvU-nQ5nauCdoSUlQ@<|rPH;)%~via%|;}7xr&wG@g^4COr zu}i+ho^U|x5Y`AIYmjwtk*Jb2I5O(&wh*ZAVfubH^p*IY;u^^D!6RiDln6L#P-F2B znsTrmXq(#nb3!FjFz?V7m_8%@t&o}jUdaFVPV`@TZUE-ZN=s-Tn+(jhZtWCAnEbhF z^1+|d&Ef|ORD`V=f6^l2uncmW;9nUf6V^4eSf9nRnOm&&7cyc>&(e@u$BL|dW|*hd zo-KUsB-u#2cqN#AXnW)d^2KT-cc@ufNJmG$W?Nj78#%)qG@1R+;E=o3~J3 z-cvpUg)V{Ob#t=2z&JN0eim{S7*!_K_iwxvJIvVVyZOmp8UxIrlfWH8pe1>_vb~I% z*JQ9Xub0liEw)Z~Hi95_kiE`8IC$|u2;#F}5NtDrNRpD)Vl@Q0k``sRMtlAa@Uy<8 zo6v(Xs?OXUV>Dgm@9G_d*sQzvNW?J=C@`b0qP^B8M3m%>L6M#LXy+F7$l`GdFze!# zlxov%3}_9pJ(JBdmiT}HPd@?EJkU$B@Y^&r)K?q??BEE!{ty@Nf|^KW&thEy*ff{} zHVyf@ZwqVsgKH50DAO=(Lqg)(p%BfTjT9&HHd+H1w!s~3{D23DB@Ibt2O@i9=s>Zn_%dfR!;YoTpv---As+lJO$5hk>v;RSVhXe=b4 z1s^-90d15TTKSD>fPNl=h@p7d91NB?H99=9cb%6hl)war^Cqg<4lnB0EY6R8t8z zc_DfWRWj6oG_kqKl>UbHWtZ=p(LrZof%mU*eR?YdY;JK|nN zmeU;PC{l`7lRhnpS%MG2K_PpPrkS@iuV66>j_AduoB;v@L3TUIm~EKj!!$?oxBvm; z@?=RS zPC7*9Pb7uUez{8UyMF~^Ms{W!jYD*E3$vCrQR@jyX;H{7aQ_8pL8$>6(SFyYJOIfE zEe4xQi?*2acN}M~R^z5Y1)XzWq20q3`X*!ESPmv0xe^AWGCA!ZRwYlXPqwyqp6?O~ zK!0&mCGWAfq(MVS+0&Z8Khrcy7$#o47gvm2BRfMF1-RUP<@EVUaB{>iG!W(V zhRHXB1Q};D!V>sBZ%m5bmfTG)lKidC8gZ%)DFg8(<`lA7qG2c^C8=8;{1l) zC4a4n*VT7Q)2SGrWdF+%9RsgxsH{8^?KRz_I>*4>pv$k9v10=dQhi?Ojem&Xn!J$T zV~zTY=7`OxYKRO)4R_AY6z7Lu*UkJyJ?9ZeK%W8mN8!#T=ZEi)lcyvxK!c*D82}nJ z6uoY#Iv4A@GI_;(>Dp$!N07BIu6WnmtqZ_z81A>KZv1shF)?Z;;n$giJ1hN1$u*l0 z0u~ZAjXv`Xv)N39kYy>ck*iR6Z#Dh-TSv&(lSPacLejngw<$W zh3uAt)Y`9x=il@uw*#N5ns6Ws!C!f+l2=kuCw%(yAH$+r6t}E}dztJD?+9H?oh#eZ zzhJSC)0x9GN}+hUl$x?y!IEnlB9Lji;uAj5@2(C@{EkQ+_*Q$3-GPp>D^U)d*E6Vb ztKwH#l0^yAj&pQ%yB$2hc4KPoZ&6mqR5-Yh2_k&7%>BF|;n0lQrtmFJVKP$8X-FZ) zF8rxI&86;flvh`0;;mCR3f}H|tOJnsYfUHy+zxH%42wf|s_Fg8?(;Z8#CP+mOoXMc zH$lJf{SI-9ZmTn7rNdbALNa>40MM7ImP4CB9PU(DrqAdA&L0h?HouBv3^7vk$4qzw z$1>89UNrQeHoxB^&O0hdxTOk4eTQ8)6XsoIYBx8xO@#}{0uj1p8!{ok4={O#@;VnB zwk?HLLJ+_a`jm0RdT5^jYp4@uM$in++emR+jS*fg>rq;5ozIi7d*0tW7i6$wp4Uj! zNqVtF z*H5YlhWgA~5~u6#&Y8%vp|6bb%1Lez@*+w&?F|K+{4}j|d2nx!_Zs@u?F5eI)$KJe z+?DcQ3(+6*q7}mfqezJoLG0o33}_0`Z=ju!?AZ-#haC|T2|@WhYLMhd`;#TTjo#RB z^~y{f*8NFT6}|9#<#7i+?LNEOJK$su6?X)cvVb0FLWQJBnVi@7Rkr_CrH`4;*Yi33?KQ8AQ`@;XO?!p!9@ zQX+|uu;HCq{&s$$t~0A$ko8Evr8CB>`6rs(i_g$*0p#AHJcqr5SX$lc!D1msMUBb*~7%?`A#`zoy! zdLWb|B}A#RZT%DrGB)@qE#BY_?-WRRW5w2``itcb`XGfJgLmtuRg}`X_zMj5!H8Cs zSXnV+7DggAS1L&!DSH0RjRen3tg2)4UbJ#!r3#V-iWHbV*eU_1bdh+!t@lveep+Q1 zQMyH2JsN_`=%e8qbZ}45^7bjYC7ZuM&F#oTI0Zppajf|G>GOngUB&0;1eVB?pzK4@ zHrdh4_Kjt$avnE^fdfxZI?P6lVeeeV+0!3dvTe2Vhq`Qpn>~F*4HKZ zDe$|eNYAbMpsuh~B%oG~(b>o{x0-VJB+l?8ZL;8;Gp2+an{OF&OI;>_hjcVM(o0>b zB|WH-^K>$LiZNFr-x^j}_h{6_kAPQY1ODRW0BW1D{(Z&avQ zDz1s3@>q5=3HKr74wWVBEK^j8YJiFRphsmURlIeK_Wn8d~ZHV~Y;w zI_rkDVUrj+4;Hy4sw>>mjM)s~ZW9_I4Fv8C*h+d<`L+tE54jF`BpF!M1_U&~Ob6xK`Z-~;L*p6Tb@ zvm|;#um&X1^Gw@jpA>SFJqPN_(`F_gR&b`*K}kV+Y#08mO~(vX+%F}fa!N0jo3RD% zeN>5B0_zsh2~JpIOc_BR5`e#v}JlYolZ{IwGNL84Ze@xY>cKkGhj&Q`J49Jl)jP_DqznQ zA!Svi4VTV=Qz;}F4(6J)d>KtC7{%FX=f|X*Yl|@>0 zkg!>l(p(?&Kje?YRfb=>!l&o6!0cCsM@d$?iVHGztL*7^^i4}DH91&bh!zHr4GOas z_Xz3a5x#~7$ugE@XX_&*26^K`X;mZ)k@hiTY0cXWkI|B#%C(ih|Hu>h>Z4+W)Al+~ zuqR4uI7xCx=14Rh`>P(H-D)*l+P404wcA;29i|<9ly7s;F zVjQ-l*G7_!>TT}R(2n-}=H+xgjoQTD_KUnY#Ylt(Va>hc?gMO zSMEOiIT2=V11xR*mpn9Ll6xfH?2YeOk*7`&&jaNw`$r$UKOF=2Fc(tZz-dS3p*5mr zTtv!7$I_-5H27Dr6kK*7HkkI00^?^aMj3OZ*s}8>5GY{xRH-{4fB(EuQUGLqZJdN5 z1I*~)1&Mo{3UQO7k&NZ$`1O4M^doY{I$-)-3|J(Tj-!|J`0iPDZX|m~v6K3I2Oquyk)eVQc)Wcphl|f1fJLAL%*^>(yn;97sWm{g1n)*ZP z!nbjrv2|)VZ}y-iEW06)#ro4kgwR%DaFE5Xq1~UAQEwq#B9PBWe1i$Z2AAJT!`8X6 zo=ID4)N%^w=Vvhr558PLoR#s%#~WF^VRkII`+PUygERbI9W4btmmNKnMT`u&@PhH_ zoJpLSca$I9w$zV7sRt7tE3cXUG_C7k1pXosoU%nsZBiyc>j|AeL3CmU=X&|iS6&m& zEwnRmMC1(i@9N3_#yU*c!NJx+!Pw5$;g2^P&`I~d-fIo2nyxsiXdi>MthL-O$gB=K z25qAYt=}vZ>}EMgI4lGN@f!MN6v+3P5(ghXyBmq*X^l1%1U9ZyvL{LA$o1kuVz-c% z<+OKeyn!z6ghHb8`87aoePhNRTjWTB#Am6S`u2Kud*0F2ad>3=`+Zj$#M54TzzG!# zHE%dKzc)mWEX-wGgn|5ELWY+P>ex_#sw_FGSD*kE_Q?&yN@<`^ULJ)LV_=n(P+P^G zG>k)H=i2FW7UHF>NX{=sftUKQDWMwZEaht{)Ttdus?2aA-$P|D-mr5wUO##$O?shg zA{~Y60g9k2QpTrRH!tAhZQ{K}L-rJGSq)hO^blKxxwy%0mutJh)B)YR zo7QAKZeQb_!(^p;qQ0u*kX4&O785#Eqo7vNUisB3V=3Tso}MBsFE}2P0nCoC9NJyw zprdh)ulcQpVel-G&Pw9fkZ0%JXc-8)3|oFG|H8EgnV@3l1WV(-om2+_{xtENu}4i3 zy3N`FVJ@$vAy{kQY|}+Me8y7YH7&dGu!65KQmmu{6T+3^f?t{McG3yx#amFuGcJ^b zd|D+Y4j?X#X!_N`J!308R0bGL$8+gW9i@Ko14cgiav8W2gQs{dmu9DZIZUI{b7lI< z%CDRwvo!7?j=Q{6>ou>9jYf)sd-63r12jx^Q?SQfvO(p2yKPRZ>yuL(bSm48O^y&I zi|jb$TIbNZ6Y3%67njsfi3vLFA!&JSZ~pyu(e%N>8ZrZH{N7|YuZ+(c>eTb2`{Y!$ zvbb$pEZjlo=|4F)5d7swl{51Up>x<>7_Tf+ z+v!32XYyCwquUB=_paj@%I)RbX-|n^ww6y{I?D{?`>3BXgD!U=VJ={y3WJKSl7k3# zl;H__gVC(igQc*20to6J>Y(APw#l)5!j@Ry;Hu)TsWq((3$0Ky)}c=g2Be4bZQ@jY zezIr{Os!xn7FMUGnfB3zM|!N#NVBDeOT~rhnmv*F4CF0tnR1f3^3ozCv-nseK-c17x;Yi<051HkqL4N zc3hcO6-JC(Kib%aqGV>+oQ9H%Qvfv<`b^zAj*!s#8%KDWPE#i)-9+piU!-Y9lggP>?#5M%xy;XtN90{ZZv%Z*ka9$Hmv) zCIjz*mz$|7p2Jvkz0xYnVy|P2FQghp2(wLxVn4;%C!yrtsK^_TbwB@x6<2WGSX{|9mC_W*uya~tOwNnp z+H>8&iM1Uq({ML!Eqh5^V}Ds&VJmeMfnIaNo4Tpp3Q9IxYxg_cJK_hdOjC+-JR)vR zv^?6eL^23?J?7V)+X|+@7?UN=xI{7&MqFVP;JXE1iqJnON;I!>{0#m?iPuj-3v-OP zDS6%(&uV+{74QVL8Eo2LG|u(NSD$y|&Djwl_dp+FWZUU?fn$~x=1fEUR%E6BbRgzw zmQTEiWcvf&zwa904cX_t-Q^)sD(6ut*y3@Tb?oGV6?1wdRm40Nq9eKl^TvV)(s;us zL!jMEpgBEN4ab1!AFvjc2e)ka2FnzRH`^f!ZGnlCuc&_^E>A403AP+4Y8T;xJkKjr zBKY&M&rXB0O6>T3eLE;8$#!WKNyBjoe?J{38fE@Bh^(s7cby$6t}WtY_Gv5+oYk}= z{G#=HbPv?@@yw1Jw?L4>4B5x%ae*9#)R0^@DtF7c_r&9)KOk zNV!Mntc7okdx705V0ATvT%p# za2!)%I7XDavb@OoGHLSK>%2}+G7{J2c&{zV%M4M_gCt_aw+}Xc*+-KuKY5naVpXjl zeP+LcNNTCYc2`}k#b#yTgkyzytBo0ltLM4@1UJc6)6;NJt1nfXBabh8Gc=ZXcs1q~ zUX45QhV>pkJ)6P_A$CCufW$x=X%B@ANLLe5J-{P5Stbiw+t_~KbfFPP?dx5z2p8N`&`e{Z8xleQ6G8k6xwFF zzh14C5$%Gb-P5o}R_T?QfDJ|=#`cv$d-%&z4VNdFprW7h2&aMpS%*M{4%TIz7w<^9 z)J%$_(?Po|Blb-X!Y^VS<1bbCu5_(4D$affk(=>$bc~-dQVb1M8o#V&+t>!=6(_I3lPgj10Q;rG=T!Jx$NB& z`GmuS`XT(vL##^^%jqA$owD7G7~0~?(gm5U*5$`_EU-Ub;|3y*Em2xsmSFMIfrgZ%QVS9}dl62M4iW)YQ626pyAa+@O=lx?XUs6>p zG_i&TOhH~rcaL{&hK5|W!2lk81Dgz|52ow+i@7g`IL|E`t3P(591_RJp%`dJ+ zSY?4O58xTBo{&#}Zfiak`1`W~w>4+~c6avQJdnS-iwd^-M&|!8T}n_5*F^>PBp_5E z5zKo|`9gJ`7&si4G>$tgv05`0Y9-$F4BQx8iR9 zn=3g=;Bj3h52XP^Dty&zAx7TAAq6iTI3CIgXf$29>v#fc29=1Sf*&FWL+>1+iZ@Tf ze2Kg2jJN5)+p9I`Q)O=aMx9y_Rj9jVJ8w&2*Uo&Uj5#~P>MtUcouP9D9R9ZAx7KJ| z^4HpYYzDJYq?jlvbyS^)LniGc`ss=>Dj?t*qBJHXE<*Q4vtm?N>LRlWax3m?e87{l z_sep-@bAHqpUQ;!?P+XM{0n2`L!0o8i_iD$KpT*}>r+kqK7^z3_&k=4_G9MhF<$zy}Y+@x=Jq=kNk3X?dXv%>^;CRkzG(jHGL!myijKqcQ3TPCS zg~L*5)cnBGGV9pFr&LQE3LEMMty_d8H%^%u{6dtgX+QF*fzPmvNHTj)O2VXK3Uas6 zh>6v}bIx)wuhm$ncB5=^f7&_^3qQ~_#WrJWmkoN3Sz<{7)A8UJ#t{>u-Uf6Re&}#a z;pgBoKX-j}ohO8T#n4ZA8>qJE#)>W(#d3N@jHwI|Bt+n>+z$FyRVggy`kE2YMQs;K zrss@#bGiEL59ZDInyglRF7RB{ihXqTWy!Nw-s!5>wL#|Xx!cO-I(^{qkkfhFA~Ua_ zbO`O~naBPQW#1TOX|`>d8D@s94BKXgZQHhOoXD_kM`YNxZQIPS*-=&R_IuTRZ}sRg z&Kcw2*SGfm=C{^fYtCuTI%HjGan#>UIc1Jmj%R)}$b<4@#x}r`%ipVQl7>+anQ9`A zhDcYs2Etw~Ov>H?EVlPbkqPl9hDd~9S8OfhQMGRj0<`Lt?;5io8EEd#5f-L*c$TRv z(xF=oBUZGZFu$TRF`}Kk-tk%6Y9>P-0>0RMg+U%sB0|ctu>^0g#$ySf@hUPj%j}We zJ7+s-<}7hs*pBja2xTuHptHRQwV>8h zQw+YX7rH`9^UZ5Q%vEvm61=T+CkpdD@N*C_4A8mL2I$;76x-!)>o zhL>Q|5RpyP7^>|ik6?DswmJi)R@m*d9N_^7!{SjoIqPb^;fmX~EbHjTj&4Kjo3zMw zP;H@VSn)=A)YS5?tu?DXsmV}I1OM-&F=;=q4yDgZ%Pa6s~z1 zZD=(VONrAi?&qk$EZHfN&giTvTR`ciurYL}s0UYC{JkxeOF8BB_3nygbqw%`X>D#) za3fjNZ~)ASL4(*hg0mFGHN_sLdq4%`p597v*^j9Hy>;YYBvrYi$4QP-24*_@N(#lz zQ`8DTyFeaMLV@DXm@QOZ)T=@u0`B-i<%g3T@ilKmE;Hn;L(u@TWtnBZlW%hFzJWhw z4dCz48@#?P8={K7m>OZ+ecLACtf&H)N=9x?iFrgXf{vIGxFE`{?Ll~lnlWsYN`ea~ z6l_a;kTMY3M6RgNOcCxd~TW%N@t@xokm4$={^#qJPW>=8qxvaEC& zfeCinT}rG*OSP(uWdTBlYK?-Oz+J!1ZK=fP3UX>FkDY)AU6fpzu4gE$clJr0bGWo1 zRp*T-i-f;HUp&vBBbz-{E_CvDB?|f3uOPLJirudlzKv|%8y3$Qv1$kAqKoL_X67EM z`N+o8UArl?XR|$wJhecFun@9?A&$M1ZhKHBV%h;wixOC>HF3*PU2#EV!$Nihl_e7A zZ&$Xp6#%t4!|KdB^cA@yRLYDAFWvQnJ4DI0ov!<$%8Ux%a#71RcalqLWq78KNW%iD zJ;IFq%{o`kG;dn+!p}o4{KXX(r|2voz9Z`fPWIf+oJ-IVw{U>4?FhHvBZ}TBvnp>B zIDx|`oP*y2+ARoUPxGr)&`_`5z?!89Et}@#vH%_Xia-PmK|2jW@Oqm)QMm5Ft|^b#+JHbroHTORAlueGN~JHs!kfvYY(-6 zl(tD$+Fw1y{LYOdTHUAZ*FiXpTCo#trsM@p?_?-?`7>XJz<=x${A>a+BmYyr{Qs>U z|6h792@3yG@T<@g(NKf}5>M!#o2k+r2@rQR`#wDvtN1&PcJ{Yyp_)nD+tCNZS1H%6 zPgzVDmxi>u5SiBDiNVjQkQaJJpO5v8uWUVOP|8(S05p~nS2F`7PE<@(Z8g{NE|VS? zNBB|+VgbOLHoWwjS+3FXgRmkhFB5zLR&CryXF2j?N#6EK5e+27>UFT;~DFr6VWGDC6NTaZ3v z{6_Z}stk8@zAM+mrbT|l_`%Jwj*-PZ1Sw{gsZ?i`e2DJXnknD!J|4jp=#k?0Qtn^B z%e!9-4XAW4_0SArVt>PeW5>6SZO3J{>4OhOPC7qs)rAwBOmrD#u1t@4Gj*$8yT z)f>r6xe>J_7>5p@%Jxtzbk-o7GcV-#HKs^9xAyan6fyLYEPbe>ozb-Vz3E%O!b(>6 zp8ZHktZc@_(8W0?&stldk20w%?Erfxc>r7;5z$y7wvQRKNk+|d#$+1YgGYOODOvRM zF233CkSXa1-)h_Jq`x@)w)Ic`CyabzdCh0K?(#WM^q;vnl>b~q{m}yYpF@EWN>&P3 zO32vQj@KEB@7jBEzG zN8yoKC*eOvu9{mVYN7?jjL+LW&e)o~5IJ3tRQq{Q@JQ`2uBlX)Mm1llpy>5K@iqH(fm4rk{Z+M#*j z>ub{Esm)~g9b>!q(C}ck>!MvFbKm?sv6^|;QISjWsyS37-df$Mm0$rzEXc8R@&?gw zk#c7W6=l72@$s#Ne6_)d4NOAT(m6RN0-)JoiOSp-Ifx0ME7JqXqf!rp zP>kjfLxg2ytK6nE6b+11VZ=%#PdT+&X(1|E^w>5{7834V94dYk4v74X#PfDgTx`1J zH`e5|kCHM{BvG`jbw<(TT6iT(w_c-DIWMSG`8i?r#+z*YlT_iictWUckK#yUt$xOO zr7I}yX#*?0l+`z!)gKEHVo=Y-5&VSB(N<|?mqhi2jZ-xQA?_Npvl4wi zoMLAgLk8!NWPA7Mi*`KRxm+XM884vXG#`jDYXwO^-}f*iZE*y{Pe1=nmHr;Y`Uqa= z$=DVm*2O8t8tM`oNx+rWtKpXh*KVRq_P|IW@RND!2Fm;lyf60$i&j5(f&*^bK`Hnp z5ia9(PNuvDfazGEAM%cLr{GoRNUUw>14@a{?nDU55t&=F2;%|tfT0)NZmf|6}Njl;m7fjBIJ#uZOF1#;i~_k z_gt_=3m)8>3HK;_1-tr_+UB=(xI;|Q1FRzp-j@mqP&EFd-f=M4*%zY=?~dif(zSwo zom^*&p?Xs--7f`sweO`c!~E_9ECe;y_znvCYAwONhoD zNX(%AZVh7R)%S)3mw?A3e*XRHPY<`B0%|mxpRJwAe&kiIz20mQOIq#pL8dScu|e>Xu*gsAa=Q>R4h{7AkuWh}1}y z``04~AKoF`?9=U<8YCR*{vx%mIn;sh@O8LW2f>(d@Bxj`A9wQ8G@PJykkjD@A<17G5^AUMKOY)9Oqd ziym&qbhv|Zxi0G~1xFk}lyZJ>ejw{TSu=qA9A)3KS%8_fWklo;X~u!PZc)lV3Q+6a zTfJMMX@s_H6pxK5E2usBRtszQEwDy|9vf1nHc?CZVxC-fg-**ltt|E4Z%JBxj&N(w z^Q#}WO@z?%_l=oR5cNDL!C?X7UMlMbCL5ON>Go!txA3_pjp|Uu!qj*cS|xHtj>F?2 zapMMV%I%&wG)$#tG2xlSKwJS!M@*IB26Js1$l-5-`r0_zulSJ|(iLvu&0mbE>{vkP zM@^GzTm)NJYe6#7&Or9f0^<$abT1ur(|xN5T)sBuSp6>%`xo@Px?v0jEmFH@THLy3kr|_dRL!c8QT2 zY~r?A%Vi`S)V3vUji5=kyry;LuPB&}BB+d=1_Ck27D zdBJ`@awbYpD>iITz_aDHi&O2SvDPW(Y#N2T3<`(W#O~dBx0Ae$+EZZ*H;yOWAOtl; zBrDOHzEqE*hhdI1(So^7WKYQQ3~u3>NP*sduSmoE$%PkR%D?-haDfjqN|TO1bQ6zF zxiC9l+Ari{0+$WIXVN8pnha|l;l@}F|XSC>6$ zbEkZGF5Jj-8aDDJG>qy*FBWqV#I!R_HaKIVXP(-c*3;uGjy6o__8Bd;XY|5*^8szu zb9l+(?=UGO44yx`iOLCsrTPO?L)EPO;VpQ7ZcD8}e18lY0D`cOekM_Fb4pmD_O<~< zVC?#{SB=h=c4z+yXfOXLf5rL#0JKkC9s>(Udp!dq*-uToKN{A5gSG-?D;Go&WN%~O zZmmRgsXoCth-u{wn2uEvrgCO69U@^+gZeDg*TL+)` zA{XN%BYa+uv*59e6pur<^V8VQ&9`?_Uo6+0ZKee!(?_PzNn0JNtg!uVa!bML5b6lW z&&D01j?iU28&`NX*DAWR?vYX5qLLrog>(`a%5<3Qol$h#FL2fQ2;KZ7O})o0F3xk(Gvn=YNY z;g_(%VwIHLJ&nLb=CW}U>D4_aNERz~@M_}p5UbXlbI0dA#Rbl4@%12!nN;b<4bMiD zyW^A)O&gve>q5acev3Imk@D{EUslSl&HYtuy6yN*;E~HU`F3>+t-4KgE6Ml=&r38I zn^yw}y_&z8Y+iNuumDPvNv%d&5}o;bDc@`u(y`3D==eJ1r{#$Po!U+>8f-Hvd=Y?N9hIkej~*-7%65XdexEaE^(FFS3(!Mxm-*h`4)9teOxi& zNj^&(WvOKNl%(0aYDu1ksPH=ktXM&0A651YF+I$NV4#$Q2#z4RRFPk@%BxB-)A%DwLg&ZPWB(ls|A>jhI%F5Rjd^0<>_{_6S@ncCq%*&)XK! z_@lE7k}(i}MIz5|B-*9u>1j?erIW{;EbAp0{B+&?VjkO_Qvlafyd(E5A^_NWe&ls3 z#=%~{VrI94D@q7D%DDS!EZJ1XJs1EBcBkfG2yPtCR%h+y&vA16= z_duXOZZ(P0q1XhdXdaIlG9=U#@w`|I2OlQl{vH2|voAsZ7HO8gJdXzYS07Nk+kWIb z1H=}%x8w>$eC7m^M?$@@aAIP;bM$SXoN?@LDFmqwwd zp^oI+KuIOZq*m=$6_K^%B`wK+r42Zp1U59Wo-E-@t3z2?0a?x1 zmMF}w1m?hv4UL49hf^C7rI2_c!8wBUfEc4^K9K>uA`OL<_Z^>ey=Y#}>K8heKHeV= zH@_@gW`y3jX7l#)geW7T4%p4A{NlD97| zz=14*^+?)Dw@F6zV8M1wo?YPd;z+Sr0&Hwf3xv*0a*+Nl3mZ zY~s9>Tt%EdpKedwg6WS{9ffX2no~FdHCO$#VKTIOKI< zvRLL?N_Hs-O<>vRpr|lshA!8H%!{5rrWs82^YCJu(37w6O04XH;DF-9L(`m`P&@U zQxKU2$(U_@H=4r2mzRd(E=LeXt z+BXIEfm_HK25-oi!v4`xgh0!m(fv|um`EwGLf5Tu0x^w{ajDE*uPOS_@1qVFI(rbF zPaq@6I~+FA6KQpw#Hz@^zX?%;d_i^W`t1W{@b?tZcNGeMqEIOiO&mh1y=y-~pTH?z zrsx=4tAJKz34-Fu86k*K{uqwA6R6C_Y|{WWW)-_vmt9Trv#i&A0$;hNpJ2D8>67W;EB>+L3a9V@5P303q@pA~bY)f4hWln$>hSbgNc|9BUtCP7bse1VM z^N^?@lxJY@^MJ_yAM36EV6ydpV}+EFgM*&Q-_asNNyA}P5&k_=UtdCQ4SujfA$6fn z(^D+35G^|dGQN-;t;lZ$3BW`57h0HnqQxr-?h#BNb9p5qGI@8+}f z2d`F-GqjJlJ2+pZI%Lf*QU>I;QC%yr)8eqnA}B@6u`Nc=%x*8ePSIUIe(N+dE@Wp3 zPuzklVu6;*R^g;on4p2Kb5pH$o250odaDo53_j~r90`|T7L#HUj(IHb=gF3Z+49H* zN2}SBG8@?~$Mrf%mjJ!$^z_!6(P($NrMx~g%))*=xECwy##`qo=>sfafs5ZK4dnOT zdLzG6zx8U-Zgxk{SQPHtug-X=XZKC3Yk6EsKH4}8PIJB%#P_&P?DooS)U%4I+SGGp zu*U8aD+`G#-!~a78K*UZ!AMkZNnM;zF@q-_a@9Uu`)V?Bw6BDLa78A{6UgSDDkrp%ej^rPw zFjf7S*RTj_W6>^>8?N9RMazPrUOcEMW_%r!64-t>s_+LtK!(#EUYRJH-`ozf+2|Ppbvv$z4D2&SVQWzk4;D$q8 zpKQo}$o86ehYfe0e|-^(Z=WELS6)6P^`B#{LhLi^S|b_mz_`6P`>s*4@E+9S>A!qP zpDrn=>zJr)H`=OV%ZXeJMw-dn(+uGJIw3tJzue2t$FealnV)74K?qJKr{{f_&%2`K zxwjnk7gz-9xj_$nf<@jxf(7BfuVobfl}|YQbsSTm0_m!`sPtZG>}E4C8Ov3JEKyBJ zTzyupJSvthXx}y*ORW)>hu};ioflbP;&wJ2&R{bjA&M_GI|bX4pEm0i=7(;ZPB6?Y zCXg+L^&MIK7}FFwu%1^93c2`c(!&NdLO8TD>LTr;<7I;LaINydmO zU)$Ho(d$({%WmkJ7aZ75tUx$g(?@xDOl_E499qcz(d#El%1Z`s8RyQ*ZMZ$G;RIJ9 zG9R*0mHoTX>+LB2M*~l(#}6Xkmn)~I=c4Q92$V;X4&u>9W>3-~n@tpe3iGQPe8i&z z4!zz>Ce5trP9p+aJ`1VH^HE-;`-}(>Aufgfh{%7wBgFeHJSwq*G9Pc8%ALfrG zk)=drrsAN}l@!I=Zp8|2Mu&0zw3Vq9j#{9OD0A~rS{NUO$^4lS z>O`VHTryGTMx`fh$Pqj?b~R@h)iZA36;plnKCLC-6)YkQ7Y=t9@f&NIk!S4TDj?76 z4fx?!2-!Rm`S_>d%+(&UQ7>`Ly@eZS#2W1VT_kKc3~l%JCaTN;<0=Z*wp1{3TYHI^ zy9zV9Olt4)ah|a|l`|8cxG5bof&_MG!S3}rVtdN29mghHvbc;>=8(Lc30DgJ?67LC z!Dz^BnUB^Yu^MqtFV~Q(9$rLKhHO=HkZOPBn!@_U0fHiVTUvg(Z&-RtD=v7>%^VJ|9b}QE9i5mOF+dIBD6LQk549JW*e3 z;h}qLN`hzY05Ulf+fhZD*1^&kA7)8;ER%N8FezNU=9o;0kuoxAaSd7qky=hE@~MW& z&85z_hKyH&hq`McLl}a#EHQG$I)-=WmR$5^SqhEta3&`9$*MZJwyXM!jS3wd=@}JL zSV^$@)g{`DEOwfD>;8=PE6<1rbt%XVp^xsug`%Fm-JwSEJ3qID2e>^Flqh6wX>wz# z8SsG&O8oW&2`HKKz?N{Ria42Qbr4Q}ll1g*Cc!F~5F$2aT7-=w%IOgIcev7}q=a3cCq#>vk}FVww!wuXy99MrTM}&h zr*}n2iQAP8GhR&KavB$o56iuTH8J9g$ei<}jk1;>7lrQVmqd(@B@g#EjWL%20VPqv zAJZl*0D+$UQZ5-li7QO7GeUMwJfThN-=7Ru%R3-vw1Uj0)_*7zVo9RR3mn(baOcuU z&o+MOIbgHD1wn|t2q!)Zi$0<#iO?``Z1{nb#v)TRLyF@YgR6a(4_j!Krk8vcD41@& z+-11n5Ib*YL6(@pCLMxJt|_dR!l`(xf_95XbhK-N4p8N2*Ur@=d4fn_5bg1=4uJ&n z^jiF+eo~sSppj_==Gvr*Ha(cp#f}t`j=eWA_5b4k?edcuEVFwSjAq0h+YVSQT|by& z@s=Gl{`eMIVG+x`T-~G?OEJ$+r)!mI-mn;JUJ8sS3|!4bVoOlahUJo-%WNypH7#eM z)L6=jZcZ45WsYQCzgU^edW&3>PBG1VQ874&qeR(CSqo~Bb~Xp5UT9svSmVld!W;{% zb>?_Ut=k{lD=nwt*D9=2a7$dyw>}1@7LB{1nsHeda=--I(a}-WFemy9Nh6v5M8yNv6a8?hY~&$Kh}>=nutF+_Df`l& z0}Q}aj3_f|Es5FWImF!`aGO%x1D+DBkh3Z@STH5gUL~Z7&46=HG8Mu3@Vc0t7i%fM zO4np$WA3<-Z9Au?Gh~hCs&$9^NDZ%9#+2H7oluV?)63vcVUxws!plO!O2$E@q+8^H zLA=^@gS1`|)J?hla}LV)Y_rt?xAOa~BT*Y2A_lrP7f(Y=oy{ZHi=j-O791CF!3KbB zb!=`oeOu*ygd#zGofWU?hG=T~iUj-~2 zRZ(7puD1=v_BYtX*G)uK4sSq@Q5($VH)BGx0F@%+(TL z?MuX|xA5~93i=^nk*S{ns!^>N8Pf!LEt+|r`JEV1Q-Q)QSEyX#c^fAKBTx6}E_^8_ z8Q~>GN0z?Wl{1<1H?ML05~s-LErF*1jl=2#h7**Q_PdQyQ+RM5+SXH)%)u3F9glV! z)KkDJ?KXh)>YdUwyyC9i?wi#5ySvZPz-UtmP_PEK1?{de9JY{p`vJvCbVjLY<%5Li zK^4y&R~YQwGZhO1Vx<3#icXHzHd=LaSAB?PWX_%U3q5;SBK&Lt(5WYf&C_6Kx$ayDxH{fcYDxoO!75=%Z`@hJe|N>7)(bN=8p)S9ThwU*Zy~kpYF_2>6N?d&auBP zp*8{@7*Lp9F)n&{>*QyYL_+g^)0;ayZpwIrui)tdvAeXet;%4K2cBTo1#@ss<~pFJ z(cptLF;i@yHyaO{bobPz^HcGFV&fM72%4WP|AC4S^Wd$i1Xrmk(; zcUv3T)ID`L%yygXJ#D5tYUX9nn6jM6TI5~jOM9ad&gfvR(oV%_hAm6PcEqXzlG!4R zU*A&4R}Nkm1>L8Ohn|C-+a>(1_qA67qQ=mgGZ;KSQ*~S;PsP@wPASwJ)n#mFg57a4 z$3&ElJ@~U%Q`VZhzJWHaZ~<$T)gMVJt96mlKQTqqk>2_o;M`rmMKY6Tp=QlIrp`GkL028%_M;Cw5((PQm~K=u%lH7-kOogb z9f<|^ZAtjn3kHu=7?)uGtEYb}rnqM;lbI>uBl49ZUxr7>pvV~&&zUfp%GyNZ;A6RO z%;dYz%I08lO;l~Ym%r}o9&#sImGkEL~SA01kpFKgG3ntVR-iyIsKm9{i#MWHllU%bb8_R68jIx|(?uoS%z zWLo7m1wt>G>NJ5`r@GkhpCpzh*O8iQ$eWkEH64|KX{NS29=6mru&?Dqff{SIpZrTD+)d#$3_^COx+0Dmi@a>IrU6 z@^2Ntlq$iW&OtHRZbaSta2#%;wt7((w#zCrf{)0%&-J&JJW z7lm7Tn1>j~^7Tm#b59xgw}$5D zkFS4_CEgD}E`(vfeCfmfr$Hame_xjUHQb}{$$~VpCt~=kGz(JDvO)axMgoSGSnQY2 zSGV|DJ%4S4Cc6s^8^R}rSy=HZRdbfCNB@rU0ZiMWyAyD=jAztV+iE5&{#?cA zGV!VUmF{uz{(iGd>Pw=HF1p>t3ls&*)tyr_!a)J%sOU83IOoJ=B#O(Kx$AQf zuz-Tb(rY|+6 z6)vw4dAK^WTXDB>Punk~Bg%nSw*vDlE6HOg7NVW#Vep=!LGvJ-Sofplek$s9_0)!f z6sB4%PS2%1>e`eZ9Y28)1s6E4sn=)#E`UC$P9?^7TS8!1r&)ouuYKA@827d&LGvX}5g82B(D0d=ks%D70iZqDKNpgal@dK`$pK z&n!2{gd{USyQ4O8qz`8gU7Xc96#u4^3b5wXf*#6hm{%BO`4KN8s#c+?_Df(hDZl6q zPVJ{$L>?bI63puU{P+37c-kH$Or{<3)M40Xgq>SCy}7_xqiN~gP{BLSB~}Lg&vOl! zF7PI)QVb4VB(>u1LQJ9V*f!=B=z36nr)SikF4sCSLz9rx5YrGTI!5DQ8*ZW0-vbCT zB)$qTk0H{NSzx&UF94m#pyd6ea<;|08l&9VjCkCPK_@2FDu3h8mEHszeyS(H1w^VK zoAo^Szc6u|(!$F_5QOcO-Xr2pSLkIelxG{ym3(KpL;GVdE*FJ@uzpI>DSY-H{`1h; zf8dD-*jU+qdUiS3SPR&HiqYAd=@I?c1Y(4olteEtyqCpnVFaH={elgx(khg2ciIjy z8H5sCg8Rlo!FueudF;IlIhjBBr**J+IxJs-J6&|fa^uZLTZY$%s~51X?>G=cF-Y-V@^<=$M9mCWFX$V$^a~+>`tCz|UR?@U7E5>?vte4v+ z)OJQn)CiA}!lTBRA~iHJ&ir=1^nr6m)m>19D6t3ui%9b?L1yafL@s0#n9RuZNffTg z<7y38Aorhb%@4kb!aW#qv3V|3GIcTo1jZ0HH^9?%)*q{B;e7Ps@Hs-5@wuY^c{S<& zJ+cV7>KQox-%I*0Cf2BQ5AP?$6!fcubh~IC_52jI)HINU>25N0%+dQ15BZH;;jH&k zqk@U%^~PLnSTuIJy}Z2xs14L-s!K+6mHDy5%mUSJ3np|e z+p=_u+$DWp92h^3Lg0W>=h~+Z`LaahAD*T}GmXZk?t4u4A{7wr3&aKUUAOLy*JN6@ z4aMWx6xT&({Pu7FU{MAL#O=J2f#N$G5+lHX*+SNX$~D*~%x1WN>dUx^Ye5gHu;qAj z`nifk_*D8?<<;L==`wtlyEh4jnm4qj0^UNMh=6hdscqWc2+7Tb>6#JY#Kt?51dZJzW@je4gbA zr5agvMAYW?jX;HsRDQB`_GYU-E%*aY1+&wH61bZ7CC1+Hs+x)eDHFv}pLZ6FuYra( z;f~Fy8xk_29+-UcsULc0 zOZlHWX5%0Cl)&Hj)c<~J{p+ItI}Cq*GH|r9ClWL>HWqUIV}$4P+kc&aD~l<7TGYL3 z1Es)&M||C-p^+2F4J4zeD5k;`k_~90jobVBIUqVggz92LqlC+4>NYh)F{w`MH$(CH zgd3^xFh-;kM&>e{dD~drTwJf*B--@xd;-KCBVsf!d@;}KrIYnh>2 z8f-_p%z@Hnc3U%Fl>Nik82ie=w{!AxLajmGGUH~q4KDzSh7281wj6RizB#NAd$tIa zKOk;JfI4O|{7SIJ^km?1nu0_NvzESNC(v4m14R?r^q6`rYesUN0=*%D8dVZX6a&EI zag%B!Ch?tJm2MCzj#z+}(;YxYzTN>WAaBPKu$<0LVy{p2clhr>m^^3v3?UIs0Wx#xejF5@8HC7$GEuW<=x3E+D`=&y|=PS!` zzcoazBteDZ+R|gaoIzc5M!Goi9Bal)qU8eh$hoxz*Soj5MU80%JiQosN!-(}v85>v z;xNu(e_?Kfef>>$G!TbE1yBw$o(SPjVxC30B!VtIg%&o+@A+%n&G5$330?^o?|#-2 zYW1Y&Yv|=vio+g$D)|(M$MNI{ofOtb@N%G5-K-do|NEc$PnV8&LFQ+}3;Z7&UVo=h z@Hsd*S$(Qy*;p%^*;wj18W{??8W{cg!(X@kC!6N4>MFhly8p0w&Cu1mk>8Wj6N6oZpo=AG;1iO#XyIXVe9L8BbaQ>bM|BfzDW6qvf zMvrVC@%3Y5l*Pq;e=@_5&ZuD9z1=wuwK{k3Xcysosrl($S+II!F*MU$ z=!PV19Xa>tvC_Lq|A6cx&qc_p#Lqx<`5k*fpxjZ}&cR*F)lDs%iDUV@*PT|gJMM?H zl`0ZOF@6U*fa5GImx5>2-0TpQE9`{g$0yV1S5F6&8p=0}$oFGzeYVzZwMgX5-rqv7 zL;4{d#LqNU(7@yzNyiibCdBwSq!Bz>q3_Z@T|;Q0233^eyQ+D~BtTU{1Tt09Y9#sF z_$;^JCR+%&aq%N8A!vlSw;1}XNS49ot?rMRxb1imxG@PJ8RVE+n1s$Q_lzezQ?)nv zHVm9WMS%<%IMHFr6Y*h>@z6n5GF3P|{Ad`z2_)p2i{*suVxM}@uPDcxu51M@xE65v z$M^aK7_PJHz7du`mm86shoY2}I8HKD+6;9$w0&o3$7p$Z{8RaxP2i$R^fTq70{y3l zH0s};SN~HGAxP%0x=O)HMdQbboFFnBcZ=(NKDu0$vi|Lk8P-6RUMuBs;Z*0rADa-7 z6zU$)R@WH!hDVpDbK}RCbUhh85j_<>(W4)LmXW6w4iXreCuT)Y_0%Bzv!ePa=YdIQ z>!L{7Hn?d)tk-l~&qdQZ#*V3js86|)dsq%qN3?-?M2=^XXZZB-Bz@WASubZ!X^7z8 ziDQbOB668^HzB(zhQ_0;sB$*xhOBXvJ#>meG!`~_?{6~Flplub_PCHDW(_=7PoV&b`pgSC@@A#zem*|oH>ZgN);de%hK75| zYB$`(Y%2{(vRq&bw!6iTp*98@s2DO-mfhDJi=wk&gc(hgkft{du=Ib1jPwlk_u}vV*U?2Pjh&9f zvN9YnBoiGLB_tecqC@JDgmO?wCf6$zXhP|+0%U7UWvHg+uv#pymSn`1w34?P+LG2E zsP}V^kE0lqSt%C;;+w8h+t!Zo8i!89I$0O?O<4aSPT73z_ zzR^WzD6{(wB^Xhl!K*MMp=S0T(^J^N3j8(I=4BO$&REWxqM~D@fFA(q@Z&0HV4KL$sKWnz;+4)_-H9DHu5LCHJTV{=f`5&~^ta9}hzD)X3%?l)U2> zJ!!-Z1#n4|m-GvGjzgr*C{!?!!qiCL;`&Rs`w1mS?b*_~X1HI@yXllw2mp zBQ4jt*;r8vh{cIQ;zbKDg7Qhh_ZeAA<2z@6?SJC4^gjx^ zUzLKD0+I?kH#DR~q7W!(AC$i=rap6l`ip-(g=mnltiNJ$XqsVA|2W`0%*y1s{tfs| z^#yJPV+G-OF6G_#y;yp(R*ltHaE3mm*~RAkCfy-3$%FOd^<{DM%K&_FQx0FZBNXE! ze?Vk909Wm#iM+QA#zEg+0NNU-8BPsJ$PHB@OEul*#dyLF9VIF62=UUNv-BsTZc(bOq zKU4umbJ6kjjNg#1B73$%5m!cOtql(ukG8D2P>x`PfgYOn2{4n_9NLT%&fzIDp1D0G8;;(7n(ax4GP%L@1t2O@sL@C<&B1gGZE@G83Z59IwbA za=$Kpz8%F%I;y6SG)(j9AYO0TL0T}zPMKp$5_N5vGfM*|o>iAH-$-fq zH|D(@t&av%Ye6LmOf0N>7bslj+#?hu)D_AWWr&4r?I7-@`aC6l`k7_W-d$&n62PzQ zkGryPQXR;(Ty`Tdk5jptNl4(R{jck}1|Xm)LO8_fvHcz#(z!AsomAj-#k;`C9JqQ) zgCdWrd@?wKI2JqldRqMkI7ND;WT&>W)75^JPED7Nom&Cn$Oy+Gm?wr$C}n0K3@9P&J1`Q)x)c~26D_8dj*bD>xBal z6tJ1e6`NDlq>E4LAc?za6$VRoH+>nuja+buK(l_gkYPEm*@=XA`f)PeM(kJin=5~d zyoH^=htkP_J`{I`{L@NIrKvNza*O*mk8bM_&fT8IYTs~?o( zz4P9%Aa**;Bd=I&PY_L45{?(0{~{KSfy7`+?`{?3#HyGOf?1A^S`^JM;w=O@G)S=` zVk~Nyie+yFWu9#dTEJ9cjm5T^zGitku!C^J?&lxk+ zv8CpC;qPFaj6oEiNUgHBUJZW4#Xo19hl6P5e+=dXIu8qZNj`WGi__maBy0)0MWcKJ zy)Ae*hhjCjH6JO5mx9Z3dj4OOyMT7 zQ+2Ca+jUyIAJz|8YpyZoz%zQ6Ku~OzLo4vcgSJH*4u;=Hw>^qP7+xxkyEJ8(LBD1! zov}Y@;r_XO4PLxn;d&f@BF+>mQ5XEE!uZB%3)PEh5*P9$ z0kgLBBu*C5-r3?jP;3bzG$hVOb~d0xGj>R7>o%xE3S{t+>x{HboT}gKpR*Y)w%u

J{i)CW|Vyp#u)ncg$RUOm9KuH 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Π`%iO}aP>9yumW1a!ct~f>0 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 load( + loaders: List List>, + onInitialItems: suspend (List) -> Unit + ): List> = coroutineScope { + val published = AtomicBoolean(false) + val lastError = AtomicReference(null) + val results = loaders.map { loader -> + async(Dispatchers.IO) { + val items = try { + loader() + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + lastError.set(e) + emptyList() + } + if (items.isNotEmpty() && published.compareAndSet(false, true)) { + onInitialItems(items) + } + items + } + }.awaitAll().filter { it.isNotEmpty() } + if (results.isEmpty()) throw lastError.get() ?: IOException("No content available for this region") + results + } +} diff --git a/app/src/main/java/com/sparktube/app/data/YtRepository.kt b/app/src/main/java/com/sparktube/app/data/YtRepository.kt index 2bb926f..7fda5b7 100644 --- a/app/src/main/java/com/sparktube/app/data/YtRepository.kt +++ b/app/src/main/java/com/sparktube/app/data/YtRepository.kt @@ -116,24 +116,17 @@ object YtRepository { NewPipe.setPreferredContentCountry(ContentCountry(countryCode)) } - /** Country based suggestion feed used on the Home tab. */ - suspend fun trending(countryCode: String): List = + /** Country feed; first useful cards can render before all three kiosks finish. */ + suspend fun trending( + countryCode: String, + onInitialItems: suspend (List) -> Unit = {} + ): List = withContext(Dispatchers.IO) { NewPipe.setPreferredContentCountry(ContentCountry(countryCode)) - var lastError: Exception? = null - val lists = HOME_KIOSKS.mapNotNull { kioskId -> - try { - fetchKiosk(kioskId) - } catch (e: Exception) { - lastError = e - emptyList() - } - }.filter { it.isNotEmpty() } - - if (lists.isEmpty()) { - throw lastError ?: IOException("No content available for this region") - } + val loaders: List List> = + HOME_KIOSKS.map { kioskId -> suspend { fetchKiosk(kioskId) } } + val lists = ProgressiveFeedLoader.load(loaders, onInitialItems) // Round-robin blend so the feed feels varied and general. val blended = mutableListOf() @@ -173,9 +166,10 @@ object YtRepository { */ suspend fun personalizedFeed( countryCode: String, - snap: RecommendEngine.Snapshot + snap: RecommendEngine.Snapshot, + onInitialItems: suspend (List) -> Unit = {} ): List = withContext(Dispatchers.IO) { - val trending = trending(countryCode) + val trending = trending(countryCode, onInitialItems) if (!snap.hasSignal) { return@withContext trending } diff --git a/app/src/main/java/com/sparktube/app/download/DownloadCenter.kt b/app/src/main/java/com/sparktube/app/download/DownloadCenter.kt index 6c5f564..d726920 100644 --- a/app/src/main/java/com/sparktube/app/download/DownloadCenter.kt +++ b/app/src/main/java/com/sparktube/app/download/DownloadCenter.kt @@ -13,7 +13,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.launch +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request @@ -50,6 +53,7 @@ object DownloadCenter { const val STATUS_PENDING = "PENDING" const val STATUS_RUNNING = "RUNNING" + const val STATUS_PAUSED = "PAUSED" const val STATUS_DONE = "DONE" const val STATUS_FAILED = "FAILED" @@ -85,7 +89,7 @@ object DownloadCenter { private val mainHandler = Handler(Looper.getMainLooper()) - private val jobIdSeq = AtomicLong(1L) + private val jobIdSeq = AtomicLong(System.currentTimeMillis()) /** Live jobs by internal job id — the replacement for DownloadManager ids. */ private val jobs = ConcurrentHashMap() @@ -104,7 +108,7 @@ object DownloadCenter { fun hasActiveDownload(context: Context, videoUrl: String): Boolean = recordsFor(context, videoUrl).any { - it.status == STATUS_PENDING || it.status == STATUS_RUNNING + it.status == STATUS_PENDING || it.status == STATUS_RUNNING || it.status == STATUS_PAUSED } /** @@ -113,21 +117,32 @@ object DownloadCenter { */ fun downloadProgress(context: Context, videoUrl: String): Int { val active = recordsFor(context, videoUrl) - .filter { it.status == STATUS_PENDING || it.status == STATUS_RUNNING } + .filter { it.status == STATUS_PENDING || it.status == STATUS_RUNNING || it.status == STATUS_PAUSED } if (active.isEmpty()) return -1 - var done = 0L - var total = 0L - active.forEach { record -> - record.downloadIds.forEach { id -> - val job = jobs[id] - if (job != null) { - done += job.progress.get() - total += job.size - } - } + val snapshots = active.map(::metrics) + return calculateDownloadMetrics( + snapshots.sumOf { it.downloadedBytes }, snapshots.map { it.totalBytes }, 0.0 + ).progressPercent + } + + /** Progress for the live jobs belonging to one record, or zero if unknown. */ + fun progressPercent(record: DownloadRecord): Int { + return metrics(record).progressPercent + } + + /** Aggregate byte/rate/ETA metrics for a record, including paired AV jobs. */ + fun metrics(record: DownloadRecord): DownloadMetrics { + val live = record.downloadIds.mapNotNull { jobs[it] } + if (live.isEmpty()) { + val lengths = record.filePaths.map { path -> File(path).takeIf { it.isFile }?.length() } + val downloaded = lengths.filterNotNull().sum() + val total = if (lengths.isNotEmpty() && lengths.all { it != null }) lengths.filterNotNull().sum() else null + return calculateDownloadMetrics(downloaded, listOf(total), 0.0) } - if (total <= 0L) return 0 - return ((done * 100) / total).toInt().coerceIn(0, 100) + val downloaded = live.sumOf { it.progress.get() } + val totals = record.downloadIds.map { id -> jobs[id]?.size?.takeIf { size -> size > 0L } } + val rate = live.filterNot { it.finished.get() || it.paused.get() }.sumOf { it.bytesPerSecond() } + return calculateDownloadMetrics(downloaded, totals, rate) } /** Deletes every download (files + registry). */ @@ -233,6 +248,24 @@ object DownloadCenter { audioJob.start() } + /** Pause/resume live transfers; partial files and byte counters are kept in memory/on disk. */ + fun togglePause(context: Context, record: DownloadRecord) = synchronized(syncLock) { + val active = record.downloadIds.mapNotNull { jobs[it] } + if (active.isEmpty() || active.all { it.finished.get() } || active.any { it.failed.get() }) { + syncLocked(context) + return@synchronized + } + val pause = !active.filterNot { it.finished.get() }.all { it.paused.get() } + active.forEach { + it.resetSpeedSample() + it.paused.set(pause) + } + LocalStore.updateDownloadStatus(context, record.downloadIds.first(), if (pause) STATUS_PAUSED else STATUS_RUNNING) + } + + /** Legacy compatibility: decimal megabits/sec as before. */ + fun speedMbps(record: DownloadRecord): Double = metrics(record).bytesPerSecond * 8.0 / 1_000_000.0 + /** Deletes the files of a record and removes it from the registry. */ fun delete(context: Context, record: DownloadRecord) = synchronized(syncLock) { record.downloadIds.forEach { id -> @@ -283,13 +316,17 @@ object DownloadCenter { recordJobs.isEmpty() -> STATUS_FAILED recordJobs.any { it.failed.get() } -> STATUS_FAILED recordJobs.all { it.finished.get() } -> STATUS_DONE + recordJobs.filterNot { it.finished.get() }.all { it.paused.get() } -> STATUS_PAUSED else -> STATUS_RUNNING } if (newStatus != record.status) { LocalStore.updateDownloadStatus(context, record.downloadIds.first(), newStatus) if (newStatus == STATUS_DONE || newStatus == STATUS_FAILED) { // Terminal state: now the live jobs can go. - recordJobs.forEach { jobs.remove(it.id) } + recordJobs.forEach { + jobs.remove(it.id) + if (newStatus == STATUS_FAILED) it.cancel() + } if (newStatus == STATUS_DONE) { announceDone(context) } @@ -323,6 +360,20 @@ object DownloadCenter { private var job: Job? = null private val cancelled = AtomicBoolean(false) + val paused = AtomicBoolean(false) + private val speedSampler = DownloadSpeedSampler() + fun bytesPerSecond(): Double = speedSampler.bytesPerSecond(System.nanoTime()) + fun resetSpeedSample() { + speedSampler.reset(System.nanoTime(), progress.get()) + } + private suspend fun awaitActive() { + while (paused.get() && !cancelled.get()) kotlinx.coroutines.delay(100) + kotlinx.coroutines.currentCoroutineContext().ensureActive() + } + private fun countBytes(n: Int) { + val cumulative = progress.addAndGet(n.toLong()) + speedSampler.onBytes(cumulative, System.nanoTime()) + } fun start() { file.parentFile?.mkdirs() @@ -333,6 +384,9 @@ object DownloadCenter { .onFailure { Log.w(TAG, "Download job #$id failed for $url", it) } .getOrElse { false } if (ok && !cancelled.get()) { + // An unknown-length component now has an exact final size; + // keep it in a paired record's aggregate while its peer runs. + if (size <= 0L) size = progress.get() finished.set(true) } else { failed.set(true) @@ -367,9 +421,10 @@ object DownloadCenter { totalSize = contentRange ?.substringAfterLast('/') ?.toLongOrNull() - ?: -1L - rangesSupported = (contentRange != null) || - (acceptRanges?.equals("bytes", ignoreCase = true) == true) + ?: if (resp.code == 200) resp.body?.contentLength()?.takeIf { it > 0L } ?: -1L else -1L + // Accept-Ranges is only an advertisement; a 200 response to + // our Range probe means ranges were not actually honored. + rangesSupported = resp.code == 206 && contentRange != null } if (totalSize <= 0L) { @@ -379,7 +434,11 @@ object DownloadCenter { } size = totalSize - if (!rangesSupported || totalSize < MIN_SIZE_FOR_SPLIT) { + if (!rangesSupported) { + size = totalSize + return@withContext plainDownload() + } + if (totalSize < MIN_SIZE_FOR_SPLIT) { return@withContext segmentedDownload(0L, totalSize - 1L) } @@ -393,14 +452,13 @@ object DownloadCenter { RandomAccessFile(file, "rw").use { raf -> raf.setLength(totalSize) } - val results = bounds.map { (start, end) -> - launchSegment(start, end) + return@withContext coroutineScope { + val results = bounds.map { (start, end) -> launchSegment(start, end) } + results.map { segJob -> + segJob.job.join() + segJob.completed.get() + }.all { it } } - // Join every segment in order; collect their success flags. - results.map { segJob -> - segJob.job.join() - segJob.completed.get() - }.all { it } } private fun newRequest(): Request.Builder = @@ -418,21 +476,27 @@ object DownloadCenter { return@withContext false } val body = resp.body ?: return@withContext false + // Full GET length can be known even when the probe omitted + // Content-Range. Never use a one-byte 206 probe's length. + size = body.contentLength().takeIf { it > 0L } ?: 0L file.outputStream().use { out -> body.byteStream().use { input -> val buf = ByteArray(64 * 1024) while (true) { if (cancelled.get()) return@withContext false + awaitActive() val read = input.read(buf) if (read < 0) break out.write(buf, 0, read) - progress.addAndGet(read.toLong()) + countBytes(read) } out.flush() } } } true + } catch (e: kotlinx.coroutines.CancellationException) { + throw e } catch (e: Exception) { Log.w(TAG, "Plain download error for $url", e) false @@ -440,41 +504,52 @@ object DownloadCenter { } /** One contiguous byte range, written at its offset. Retries a few times. */ - private fun launchSegment(start: Long, end: Long): SegmentJob { + private fun kotlinx.coroutines.CoroutineScope.launchSegment(start: Long, end: Long): SegmentJob { val completedFlag = AtomicBoolean(false) - val segJob = DownloadCenter.scope.launch(Dispatchers.IO) { + val segJob = launch(Dispatchers.IO) { var attempt = 0 + var written = 0L while (attempt <= CHUNK_RETRIES && !cancelled.get()) { try { + awaitActive() + currentCoroutineContext().ensureActive() val request = newRequest() - .header("Range", "bytes=$start-$end") + .header("Range", "bytes=${start + written}-$end") .build() DownloadCenter.client.newCall(request).execute().use { resp -> - if (!resp.isSuccessful) throw IOException("HTTP ${resp.code}") + val range = resp.header("Content-Range") + val match = range?.let { Regex("bytes (\\d+)-(\\d+)/(\\d+|\\*)").matchEntire(it) } + if (resp.code != 206 || match == null || + match.groupValues[1].toLongOrNull() != start + written || + match.groupValues[2].toLongOrNull() != end || + (match.groupValues[3] != "*" && match.groupValues[3].toLongOrNull() != size)) { + throw IOException("Invalid range response HTTP ${resp.code}: $range") + } val body = resp.body ?: throw IOException("Empty body") RandomAccessFile(file, "rw").use { raf -> - raf.seek(start) + raf.seek(start + written) val input = body.byteStream() val buf = ByteArray(64 * 1024) - var written = 0L - val expected = end - start + 1 + val expected = end - start + 1L while (written < expected) { - if (cancelled.get()) return@launch + if (cancelled.get()) throw kotlinx.coroutines.CancellationException("Download cancelled") + awaitActive() val read = input.read(buf, 0, minOf(buf.size.toLong(), expected - written).toInt()) if (read < 0) throw IOException("Unexpected end of stream") raf.write(buf, 0, read) written += read.toLong() - progress.addAndGet(read.toLong()) + countBytes(read) } } completedFlag.set(true) return@launch } + } catch (e: kotlinx.coroutines.CancellationException) { + throw e } catch (e: Exception) { attempt++ if (attempt > CHUNK_RETRIES) break - // Exponential-ish backoff between retries. - try { Thread.sleep(300L * attempt) } catch (_: InterruptedException) {} + kotlinx.coroutines.delay(300L * attempt) } } } @@ -484,9 +559,11 @@ object DownloadCenter { /** Single-range convenience wrapper (small files). */ private suspend fun segmentedDownload(start: Long, end: Long): Boolean { RandomAccessFile(file, "rw").use { raf -> raf.setLength(end + 1) } - val seg = launchSegment(start, end) - seg.job.join() - return seg.completed.get() + return coroutineScope { + val seg = launchSegment(start, end) + seg.job.join() + seg.completed.get() + } } } diff --git a/app/src/main/java/com/sparktube/app/download/DownloadMetrics.kt b/app/src/main/java/com/sparktube/app/download/DownloadMetrics.kt new file mode 100644 index 0000000..f02ab17 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/download/DownloadMetrics.kt @@ -0,0 +1,73 @@ +package com.sparktube.app.download + +/** Pure transfer metrics helpers; time values are monotonic nanoseconds. */ +data class DownloadMetrics( + val downloadedBytes: Long, + val totalBytes: Long?, + val bytesPerSecond: Double, + val remainingSeconds: Long? +) { + val progressPercent: Int + get() = totalBytes?.takeIf { it > 0L } + ?.let { (downloadedBytes.coerceAtLeast(0L).coerceAtMost(it).toDouble() / it.toDouble() * 100.0).toInt().coerceIn(0, 100) } + ?: 0 +} + +/** + * Samples cumulative byte-counter updates, not UI getter calls. A gap with no + * transfer updates is considered a stall and reports zero speed. + */ +internal class DownloadSpeedSampler( + private val minSampleNanos: Long = 250_000_000L, + private val staleAfterNanos: Long = 1_500_000_000L +) { + private var lastBytes = 0L + private var lastUpdateNanos: Long? = null + private var rate = 0.0 + + @Synchronized + fun onBytes(cumulativeBytes: Long, nowNanos: Long) { + // Parallel writers may reach this monitor out of counter-update order. + if (cumulativeBytes < lastBytes) return + val previousTime = lastUpdateNanos + if (previousTime != null && nowNanos > previousTime) { + val elapsed = nowNanos - previousTime + if (elapsed >= minSampleNanos) { + rate = ((cumulativeBytes - lastBytes).coerceAtLeast(0L)).toDouble() * 1_000_000_000.0 / elapsed + lastBytes = cumulativeBytes + lastUpdateNanos = nowNanos + return + } + } + if (previousTime == null) { + lastBytes = cumulativeBytes + lastUpdateNanos = nowNanos + } + } + + @Synchronized + fun bytesPerSecond(nowNanos: Long): Double = + if (lastUpdateNanos == null || nowNanos - lastUpdateNanos!! > staleAfterNanos) 0.0 else rate + + @Synchronized + fun reset(nowNanos: Long, cumulativeBytes: Long = 0L) { + lastBytes = cumulativeBytes + lastUpdateNanos = nowNanos + rate = 0.0 + } +} + +internal fun calculateDownloadMetrics( + downloadedBytes: Long, + totals: List, + bytesPerSecond: Double +): DownloadMetrics { + val validTotals = totals.filterNotNull().filter { it > 0L } + // Any missing component makes the aggregate total unknown. + val total = if (totals.isNotEmpty() && totals.all { it != null && it > 0L }) validTotals.sum() else null + val downloaded = downloadedBytes.coerceAtLeast(0L) + val eta = if (total != null && bytesPerSecond > 0.0 && bytesPerSecond.isFinite()) { + kotlin.math.ceil((total - downloaded).coerceAtLeast(0L).toDouble() / bytesPerSecond).toLong() + } else null + return DownloadMetrics(downloaded, total, bytesPerSecond.takeIf { it.isFinite() && it > 0.0 } ?: 0.0, eta) +} diff --git a/app/src/main/java/com/sparktube/app/download/DownloadSharing.kt b/app/src/main/java/com/sparktube/app/download/DownloadSharing.kt new file mode 100644 index 0000000..5fc8b92 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/download/DownloadSharing.kt @@ -0,0 +1,50 @@ +package com.sparktube.app.download + +import android.content.ClipData +import android.content.Context +import android.content.Intent +import android.widget.Toast +import androidx.core.content.FileProvider +import com.sparktube.app.R +import com.sparktube.app.data.DownloadRecord +import java.io.File + +object DownloadSharing { + fun share(context: Context, record: DownloadRecord) { + val files = record.filePaths.map(::File).filter { it.isFile && it.canRead() } + if (files.isEmpty()) { + Toast.makeText(context, R.string.download_file_missing, Toast.LENGTH_SHORT).show() + return + } + try { + val uris = ArrayList(files.map { + FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", it) + }) + val intent = if (uris.size == 1) Intent(Intent.ACTION_SEND).apply { + type = mime(files.first()) + putExtra(Intent.EXTRA_STREAM, uris.first()) + } else Intent(Intent.ACTION_SEND_MULTIPLE).apply { + // AV downloads are two separate tracks, not a muxed movie. + type = "*/*" + putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) + } + intent.clipData = ClipData.newUri(context.contentResolver, record.title, uris.first()).apply { + uris.drop(1).forEach { addItem(ClipData.Item(it)) } + } + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + context.startActivity(Intent.createChooser(intent, context.getString(R.string.download_share))) + } catch (_: Exception) { + Toast.makeText(context, R.string.download_share_unavailable, Toast.LENGTH_SHORT).show() + } + } + + private fun mime(file: File): String = when (file.extension.lowercase()) { + "mp4", "m4v" -> "video/mp4" + "m4a" -> "audio/mp4" + "mp3" -> "audio/mpeg" + "aac" -> "audio/aac" + "ogg", "opus" -> "audio/ogg" + "wav" -> "audio/wav" + else -> "application/octet-stream" + } +} diff --git a/app/src/main/java/com/sparktube/app/playback/NotificationTransport.kt b/app/src/main/java/com/sparktube/app/playback/NotificationTransport.kt new file mode 100644 index 0000000..db5dc14 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/playback/NotificationTransport.kt @@ -0,0 +1,37 @@ +package com.sparktube.app.playback + +import androidx.media3.common.Player + +/** Notification taps are discrete commands, not headset single/double-tap gestures. */ +internal object NotificationTransport { + // Android KeyEvent constants; kept numeric here so the dispatch policy is JVM-testable. + private const val ACTION_DOWN = 0 + private const val KEYCODE_MEDIA_PLAY_PAUSE = 85 + + fun handle( + player: Player, + notificationController: Boolean, + action: Int, + keyCode: Int, + repeatCount: Int + ): Boolean { + if (!notificationController || action != ACTION_DOWN || keyCode != KEYCODE_MEDIA_PLAY_PAUSE) { + return false + } + // Do not turn held-key repeats into more toggles or let them enter Media3's timer. + if (repeatCount != 0 || !player.isCommandAvailable(Player.COMMAND_PLAY_PAUSE)) return true + val state = player.playbackState + if (player.playWhenReady && state != Player.STATE_IDLE && state != Player.STATE_ENDED) { + player.pause() + } else { + // Preserve native Play behavior for an idle or completed source. + if (state == Player.STATE_IDLE && player.isCommandAvailable(Player.COMMAND_PREPARE)) { + player.prepare() + } else if (state == Player.STATE_ENDED && player.isCommandAvailable(Player.COMMAND_SEEK_TO_DEFAULT_POSITION)) { + player.seekToDefaultPosition() + } + player.play() + } + return true // Consumed: Media3 must not delay or execute this tap a second time. + } +} diff --git a/app/src/main/java/com/sparktube/app/playback/PendingPlaybackRequest.kt b/app/src/main/java/com/sparktube/app/playback/PendingPlaybackRequest.kt new file mode 100644 index 0000000..81a96ae --- /dev/null +++ b/app/src/main/java/com/sparktube/app/playback/PendingPlaybackRequest.kt @@ -0,0 +1,32 @@ +package com.sparktube.app.playback + +/** User intent while a video URL is resolving; stale jobs may not clear a newer request. */ +internal class PendingPlaybackRequest { + private var generation = 0L + var active = false + private set + var playWhenReady = true + private set + + fun begin(): Long { + generation++ + active = true + playWhenReady = true + return generation + } + + fun setPlayWhenReady(value: Boolean) { + playWhenReady = value + } + + fun complete(token: Long): Boolean { + if (token != generation || !active) return false + active = false + return true + } + + fun cancel() { + generation++ + active = false + } +} diff --git a/app/src/main/java/com/sparktube/app/playback/PipSessionState.kt b/app/src/main/java/com/sparktube/app/playback/PipSessionState.kt new file mode 100644 index 0000000..3af5b7b --- /dev/null +++ b/app/src/main/java/com/sparktube/app/playback/PipSessionState.kt @@ -0,0 +1,28 @@ +package com.sparktube.app.playback + +/** Main-thread PiP ownership. Late callbacks from a retired page cannot clear a new owner. */ +internal class PipSessionState { + private var owner: T? = null + private var closeOwner: (() -> Unit)? = null + val active: Boolean get() = owner != null + + fun claim(candidate: T, onClose: () -> Unit) { + if (owner !== candidate) close() + owner = candidate + closeOwner = onClose + } + + fun release(candidate: T) { + if (owner !== candidate) return + owner = null + closeOwner = null + } + + fun close() { + val close = closeOwner + // Clear before invoking Android lifecycle code: finish can re-enter callbacks. + owner = null + closeOwner = null + close?.invoke() + } +} diff --git a/app/src/main/java/com/sparktube/app/playback/PlaybackCenter.kt b/app/src/main/java/com/sparktube/app/playback/PlaybackCenter.kt index 2545fc6..71de64a 100644 --- a/app/src/main/java/com/sparktube/app/playback/PlaybackCenter.kt +++ b/app/src/main/java/com/sparktube/app/playback/PlaybackCenter.kt @@ -17,6 +17,7 @@ import androidx.media3.datasource.DataSpec import androidx.media3.datasource.DefaultDataSource import androidx.media3.datasource.DefaultHttpDataSource import androidx.media3.datasource.ResolvingDataSource +import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.DefaultMediaSourceFactory @@ -185,6 +186,9 @@ object PlaybackCenter { fun onCatalogReady() {} fun onPlaybackStateChanged(isPlaying: Boolean) {} fun onQueueChanged() {} + fun onSleepTimerChanged() {} + fun onSleepTimerExpired() {} + fun onVideoFrameRendered() {} fun onError(message: String) {} /** A resolved item turned out to be live content — see [LiveContentException]. */ @@ -210,7 +214,75 @@ object PlaybackCenter { private const val RADIO_REFILL_BELOW = 10 private const val RADIO_MIN_UPCOMING = 15 private var playerRef: ExoPlayer? = null + private val sleepTimer = SleepTimerState() + private var sleepTimerJob: Job? = null + private val timerHandler = android.os.Handler(android.os.Looper.getMainLooper()) + private val timerAlarm = android.app.AlarmManager.OnAlarmListener { checkSleepTimer() } + val sleepTimerMode: SleepTimerState.Mode get() = sleepTimer.mode + var sleepTimerMinutes: Int = 0 + private set + val sleepTimerRemainingMs: Long? get() = sleepTimer.remainingMs(SystemClock.elapsedRealtime()) + + /** Shared timer survives page collapse, mode changes, pause and background playback. */ + fun setSleepTimer(minutes: Int) { + require(minutes in listOf(10, 15, 20, 30, 45, 60)) + clearSleepTimer() + sleepTimer.setDuration(minutes, SystemClock.elapsedRealtime()) + sleepTimerMinutes = minutes + // Listener alarms need no exact-alarm permission and wake the live playback process. + // The monotonic coroutine is also retained as a foreground/service fallback. + try { + val alarms = appContext.getSystemService(Context.ALARM_SERVICE) as android.app.AlarmManager + alarms.setExact(android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP, + sleepTimer.deadlineMs!!, "SparkTube sleep timer", timerAlarm, timerHandler) + } catch (_: SecurityException) { /* OEM restriction: service ticker still checks. */ } + sleepTimerJob = scope.launch { + while (isActive && sleepTimer.mode == SleepTimerState.Mode.DURATION) { + if (checkSleepTimer()) break + delay(minOf(sleepTimerRemainingMs ?: 1000L, 1000L).coerceAtLeast(1L)) + } + } + notify { it.onSleepTimerChanged() } + } + + fun setSleepTimerEndOfItem() { + if (!hasMedia) return + clearSleepTimer() + sleepTimer.setEndOfItem(currentEntry?.url) + // Pause before the native music playlist can start its next item. + applyMusicAutoplay() + notify { it.onSleepTimerChanged() } + if (playerRef?.playbackState == Player.STATE_ENDED) expireSleepTimer() + } + + fun clearSleepTimer() { + sleepTimerJob?.cancel() + sleepTimerJob = null + if (this::appContext.isInitialized) { + (appContext.getSystemService(Context.ALARM_SERVICE) as android.app.AlarmManager).cancel(timerAlarm) + } + sleepTimer.clear() + sleepTimerMinutes = 0 + applyMusicAutoplay() + notify { it.onSleepTimerChanged() } + } + + private fun checkSleepTimer(): Boolean { + if (!sleepTimer.isExpired(SystemClock.elapsedRealtime())) return false + expireSleepTimer() + return true + } + + private fun expireSleepTimer() { + // Cancel resolving/watchdogs as well: a pending network result must not restart playback. + stopPlayback() + notify { it.onSleepTimerExpired() } + } + private var resolveJob: Job? = null + private val pendingVideo = PendingPlaybackRequest() + private val videoRequestPending: Boolean + get() = mode == Mode.VIDEO && pendingVideo.active // ----- Startup watchdog (auto quality downshift) ----- @@ -223,6 +295,9 @@ object PlaybackCenter { * stalls right after starting, it drops ONE quality tier and rebuilds the * source (keeping the position mid-play) until playback actually flows. */ + private var frameWatchJob: Job? = null + private var videoFrameSeen = false + private var frameRecoveryStep = 0 private var watchdogJob: Job? = null private var watchdogDownshifts = 0 @@ -276,7 +351,15 @@ object PlaybackCenter { @Volatile var mode: Mode = Mode.NONE private set - @Volatile var inPip: Boolean = false + private val pipSession = PipSessionState() + val inPip: Boolean get() = pipSession.active + + fun setPipState(view: PlayerView, active: Boolean, onClose: () -> Unit = {}) { + if (active) pipSession.claim(view, onClose) else pipSession.release(view) + } + + /** Explicit playback/page replacement only; automatic queue advance keeps PiP alive. */ + fun closePreviousPip() = pipSession.close() @Volatile var audioOnlyMode: Boolean = false private set @@ -353,7 +436,7 @@ object PlaybackCenter { val player: ExoPlayer get() { check(this::appContext.isInitialized) { "PlaybackCenter.init() not called" } - return playerRef ?: ExoPlayer.Builder(appContext) + return playerRef ?: ExoPlayer.Builder(appContext, DefaultRenderersFactory(appContext).setEnableDecoderFallback(true)) // IMPORTANT: player.setMediaItems() (music playlist!) builds // its MediaSources through this factory. Without it the // player falls back to its own default data source chain, @@ -398,15 +481,40 @@ object PlaybackCenter { } } + /** Stable facade for the media session and in-app player controls. */ + val sessionPlayer: Player + get() { + val raw = player + return forwardingPlayer?.takeIf { forwardingFor === raw } + ?: QueueForwardingPlayer(raw).also { + forwardingPlayer = it + forwardingFor = raw + } + } + private val playerListener = object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { notify { it.onPlaybackStateChanged(isPlaying) } } + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + if (!playWhenReady && reason == Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM && + sleepTimer.shouldStopAtEnd(currentEntry?.url)) { + expireSleepTimer() + return + } + notify { it.onPlaybackStateChanged(isPlaying) } + } + override fun onPlaybackStateChanged(playbackState: Int) { + if (playbackState == Player.STATE_ENDED && sleepTimer.shouldStopAtEnd(currentEntry?.url)) { + expireSleepTimer() + return + } + notify { it.onPlaybackStateChanged(isPlaying) } // A playlist always continues to its next video (like YouTube); // the autoplay setting only governs the related-videos queue. - if (playbackState == Player.STATE_ENDED && mode == Mode.VIDEO && + if (!videoRequestPending && playbackState == Player.STATE_ENDED && mode == Mode.VIDEO && (playlistMode || AppPrefs.videoAutoplayNext) ) { advance() @@ -415,6 +523,11 @@ object PlaybackCenter { override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { if (mode != Mode.AUDIO) return + if (sleepTimer.shouldStopOnTransition(currentEntry?.url, + reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO || reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT)) { + expireSleepTimer() + return + } val index = playerRef?.currentMediaItemIndex ?: return if (index < 0 || index >= queue.size) return queueIndex = index @@ -429,10 +542,22 @@ object PlaybackCenter { } override fun onPlayerError(error: PlaybackException) { - onPlayerFailed(error) + // A paused outgoing source can still fail while the next URL resolves. + if (!videoRequestPending) onPlayerFailed(error) + } + + override fun onTracksChanged(tracks: androidx.media3.common.Tracks) { + android.util.Log.i("SparkTubeVideo", "tracks videoSelected=${tracks.isTypeSelected(C.TRACK_TYPE_VIDEO)} videoSupported=${tracks.isTypeSupported(C.TRACK_TYPE_VIDEO)} adaptive=$dashActive") + } + + override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) { + android.util.Log.i("SparkTubeVideo", "size=${videoSize.width}x${videoSize.height}") } override fun onRenderedFirstFrame() { + videoFrameSeen = true + frameWatchJob?.cancel() + notify { it.onVideoFrameRendered() } if (mode != Mode.VIDEO || sourceAppliedAt == 0L) return val ms = SystemClock.elapsedRealtime() - sourceAppliedAt android.util.Log.i("SparkTubeStartup", "first frame after ${ms}ms (adaptive=$dashActive)") @@ -449,6 +574,12 @@ object PlaybackCenter { } private fun notify(block: (Listener) -> Unit) { + // An explicit new item cancels only end-of-item; timed countdown spans the queue. + if (sleepTimer.mode == SleepTimerState.Mode.END_OF_ITEM && + !sleepTimer.shouldStopAtEnd(currentEntry?.url)) { + clearSleepTimer() + } + forwardingPlayer?.refreshSessionState() val snapshot = listeners.toList() snapshot.forEach(block) } @@ -485,6 +616,8 @@ object PlaybackCenter { /** Kills the current playback instantly so sounds never overlap. */ private fun stopPlayerNow() { + pendingVideo.cancel() + retainedVideoSourceForAudio = false playerRef?.run { stop() clearMediaItems() @@ -496,6 +629,7 @@ object PlaybackCenter { /** Starts a video (watch page). Builds a YouTube-like autoplay queue. */ fun playVideo(entry: QueueEntry, extraQueue: List = emptyList()) { initFromApp() + closePreviousPip() stopPlayerNow() mode = Mode.VIDEO radioMode = false @@ -514,6 +648,7 @@ object PlaybackCenter { fun playPlaylist(entries: List, startIndex: Int = 0) { initFromApp() if (entries.isEmpty()) return + closePreviousPip() stopPlayerNow() mode = Mode.VIDEO radioMode = false @@ -537,6 +672,7 @@ object PlaybackCenter { fun playMusicQueue(entries: List, startIndex: Int = 0, allowRadio: Boolean = false) { initFromApp() if (entries.isEmpty()) return + closePreviousPip() val start = startIndex.coerceIn(0, entries.size - 1) // The player playlist is the tapped song + everything after it, so // player indexes and queue indexes always match. @@ -611,12 +747,10 @@ object PlaybackCenter { } fun togglePlayPause() { - val p = playerRef ?: return - if (p.isPlaying) { - p.pause() - } else { - p.play() - } + if (playerRef == null) return + val p = sessionPlayer + // isPlaying is false during buffering; toggle the user's intent instead. + if (p.playWhenReady) p.pause() else p.play() } fun seekTo(positionMs: Long) { @@ -628,6 +762,23 @@ object PlaybackCenter { val isPlaying: Boolean get() = playerRef?.isPlaying ?: false val isBuffering: Boolean get() = playerRef?.playbackState == Player.STATE_BUFFERING + /** Retry the current item without discarding the user's queue or resolving a file URL online. */ + fun retryCurrentVideo() { + val entry = currentEntry ?: return + if (mode != Mode.VIDEO) return + frameRecoveryStep = 0 + if (entry.url.startsWith("file://")) { + val record = LocalStore.downloads(appContext).firstOrNull { + it.filePaths.firstOrNull()?.let { path -> "file://$path" == entry.url } == true + } + if (record == null || !startLocalFiles(record)) { + notify { it.onError("Downloaded file is missing") } + } + } else { + resolveCurrent() + } + } + fun setSpeed(speed: Float) { playbackSpeed = speed playerRef?.setPlaybackSpeed(speed) @@ -635,7 +786,7 @@ object PlaybackCenter { /** Music settings: pause between songs instead of auto-advancing. */ fun applyMusicAutoplay() { - playerRef?.setPauseAtEndOfMediaItems(!AppPrefs.musicAutoplayNext) + playerRef?.setPauseAtEndOfMediaItems(sleepTimer.mode == SleepTimerState.Mode.END_OF_ITEM || !AppPrefs.musicAutoplayNext) } fun next() { @@ -643,7 +794,7 @@ object PlaybackCenter { Mode.AUDIO -> playerRef?.run { if (hasNextMediaItem()) seekToNextMediaItem() } - Mode.VIDEO -> if (queueIndex < queue.size - 1) { + Mode.VIDEO -> if (QueueNavigationPolicy.hasNext(queueIndex, queue.size)) { queueIndex++ resolveCurrent() } @@ -670,6 +821,11 @@ object PlaybackCenter { } fun stopPlayback() { + pendingVideo.cancel() + retainedVideoSourceForAudio = false + clearSleepTimer() + watchdogJob?.cancel() + frameWatchJob?.cancel() resolveJob?.cancel() prefetchJob?.cancel() pendingMusic.clear() @@ -682,6 +838,7 @@ object PlaybackCenter { queue.clear() queueIndex = -1 catalog = null + setResolving(false) notify { it.onQueueChanged() } notify { it.onItemChanged(null) } notify { it.onPlaybackStateChanged(false) } @@ -703,6 +860,7 @@ object PlaybackCenter { // Explicit "Auto" beats the settings default for this session. autoPicked = height == null } + frameRecoveryStep = 0 // Explicit user action: give adaptive streams another chance even // if an earlier error had dropped us to a muxed fallback. usedFallback = false @@ -723,19 +881,54 @@ object PlaybackCenter { notify { it.onCatalogReady() } } - /** Turns the current video into an audio-only background stream. */ + private var retainedVideoSourceForAudio = false + + /** Instant presentation switch: retain the source, position and playback intent. */ fun switchToAudioOnly() { + if (mode != Mode.VIDEO || audioOnlyMode) return + retainedVideoSourceForAudio = true audioOnlyMode = true - rebuildSource(keepPosition = true) + frameWatchJob?.cancel() + watchdogJob?.cancel() + // Only the screen changes. Do not stop/prepare/seek or replace the source. notify { it.onAudioOnlyChanged(true) } } - /** Rebuilds the full video source after background-audio mode. */ + /** Reattach retained video immediately; rebuild only if an audio-only source was loaded later. */ fun switchBackToVideo() { if (mode != Mode.VIDEO) return audioOnlyMode = false + frameRecoveryStep = 0 + if (selectedHeight == -1) selectedHeight = null + if (retainedVideoSourceForAudio) { + retainedVideoSourceForAudio = false + } else { + rebuildSource(keepPosition = true) + } + notify { it.onAudioOnlyChanged(false) } + } + + /** Convert an online music item to its video without resetting position or queue. */ + suspend fun switchMusicToVideo(): Boolean { + val entry = currentEntry ?: return false + if (entry.url.startsWith("file://")) return false + val resolved = StreamCatalog(YtRepository.streamInfo(entry.url)) + if (currentEntry?.url != entry.url || (resolved.videoOnly.isEmpty() && resolved.muxed.isEmpty())) return false + resolveJob?.cancel() + prefetchJob?.cancel() + mode = Mode.VIDEO + radioMode = false + playlistMode = true + audioOnlyMode = false + catalog = resolved + selectedHeight = null + selectedAudioTrackId = null + usedFallback = false + pendingMusic.clear() rebuildSource(keepPosition = true) notify { it.onAudioOnlyChanged(false) } + notify { it.onCatalogReady() } + return true } // ----- Favorites ----- @@ -757,6 +950,11 @@ object PlaybackCenter { fun playDownload(record: DownloadRecord) { initFromApp() if (record.filePaths.isEmpty()) return + resolveJob?.cancel() + pendingVideo.cancel() + setResolving(false) + frameWatchJob?.cancel() + frameRecoveryStep = 0 mode = if (record.type == DownloadCenter.TYPE_AUDIO) Mode.AUDIO else Mode.VIDEO audioOnlyMode = record.type == DownloadCenter.TYPE_AUDIO radioMode = false @@ -788,6 +986,7 @@ object PlaybackCenter { /** Prepares and starts the finished download's files on the player. False when none exist. */ private fun startLocalFiles(record: DownloadRecord): Boolean { + retainedVideoSourceForAudio = false val sources = record.filePaths .filter { File(it).exists() } .map { path -> @@ -797,12 +996,14 @@ object PlaybackCenter { } if (sources.isEmpty()) return false val source = if (sources.size == 1) sources[0] else MergingMediaSource(*sources.toTypedArray()) + videoFrameSeen = false player.run { setMediaSource(source, 0L) prepare() - playWhenReady = true + playWhenReady = if (videoRequestPending) pendingVideo.playWhenReady else true setPlaybackSpeed(playbackSpeed) } + startFrameWatch() return true } @@ -834,6 +1035,12 @@ object PlaybackCenter { private fun resolveCurrent() { val entry = currentEntry ?: return resolveJob?.cancel() + val requestToken = pendingVideo.begin() + // Acknowledge queue selection now, not after online stream extraction. + // The previous stream must not keep sounding like the newly selected item. + playerRef?.pause() + frameWatchJob?.cancel() + frameRecoveryStep = 0 // A previous video's startup watchdog must not act on this one, and // its quality drop applies to that video only. watchdogJob?.cancel() @@ -885,7 +1092,8 @@ object PlaybackCenter { } } } finally { - setResolving(false) + // A cancelled older resolve must not clear a newer selection. + if (pendingVideo.complete(requestToken)) setResolving(false) } } } @@ -1098,13 +1306,15 @@ object PlaybackCenter { return } sourceAppliedAt = SystemClock.elapsedRealtime() + videoFrameSeen = false player.run { setMediaSource(source, 0L) prepare() - playWhenReady = true + playWhenReady = if (videoRequestPending) pendingVideo.playWhenReady else true setPlaybackSpeed(speed) } startStartupWatchdog() + startFrameWatch() } /** @@ -1130,11 +1340,16 @@ object PlaybackCenter { val dt = (now - lastPollAt).coerceAtMost(1_000) lastPollAt = now val p = playerRef ?: break + if (!p.playWhenReady) { + preparedAt = now + stallSince = 0L + continue + } when (p.playbackState) { Player.STATE_IDLE, Player.STATE_ENDED -> return@launch Player.STATE_READY -> { if (readyAt == 0L) readyAt = now - if (p.isPlaying) { + if (p.isPlaying && videoFrameSeen) { stallSince = 0L healthyMs += dt // Connection proven: 15s of uninterrupted playback. @@ -1208,18 +1423,21 @@ object PlaybackCenter { val entry = currentEntry ?: return val c = catalog ?: return val position = if (keepPosition) positionMs else 0L - val wasPlaying = playerRef?.isPlaying ?: true + val wasPlaying = playerRef?.playWhenReady ?: true val source = buildSource(entry, c) ?: return sourceAppliedAt = SystemClock.elapsedRealtime() + videoFrameSeen = false player.run { setMediaSource(source, position) prepare() - if (wasPlaying || !keepPosition) playWhenReady = true + playWhenReady = wasPlaying || !keepPosition setPlaybackSpeed(playbackSpeed) } + startFrameWatch() } private fun buildSource(entry: QueueEntry, c: StreamCatalog): MediaSource? { + retainedVideoSourceForAudio = false val metadata = MediaMetadata.Builder() .setTitle(entry.title) .setArtist(entry.uploader) @@ -1279,9 +1497,7 @@ object PlaybackCenter { if (video != null) { return progressiveSource(MediaItem.Builder().setUri(audioUri(video)).setMediaMetadata(metadata).build()) } - if (audio != null) { - return progressiveSource(MediaItem.Builder().setUri(audioUri(audio)).setMediaMetadata(metadata).build()) - } + // Never silently play audio-only while the UI says Video. return null } @@ -1293,6 +1509,8 @@ object PlaybackCenter { private fun applyVideoCap(capHeight: Int) { val p = playerRef ?: player val builder = p.trackSelectionParameters.buildUpon() + .setTrackTypeDisabled(C.TRACK_TYPE_VIDEO, false) + .setPreferredVideoMimeTypes("video/avc", "video/x-vnd.on2.vp9", "video/av01") if (capHeight > 0) builder.setMaxVideoSize(Int.MAX_VALUE, capHeight) else builder.clearVideoSizeConstraints() p.trackSelectionParameters = builder.build() @@ -1544,20 +1762,172 @@ object PlaybackCenter { private class QueueForwardingPlayer(player: ExoPlayer) : ForwardingPlayer(player) { + private val listenerBridges = mutableMapOf() + private var lastCommands: Player.Commands? = null + private var reportedPlayWhenReady = super.getPlayWhenReady() + private var reportedPlaybackState = super.getPlaybackState() + private var reportedIsPlaying = super.isPlaying() + private var reportedMetadata = super.getMediaMetadata() + + // This is the logical queue player seen by the session. While extracting + // a new video's URL it is buffering, not still playing the old video. + override fun getPlayWhenReady(): Boolean = + if (videoRequestPending) pendingVideo.playWhenReady else super.getPlayWhenReady() + + override fun getPlaybackState(): Int = + if (videoRequestPending) Player.STATE_BUFFERING else super.getPlaybackState() + + override fun isPlaying(): Boolean = !videoRequestPending && super.isPlaying() + + override fun getMediaMetadata(): MediaMetadata { + if (!videoRequestPending) return super.getMediaMetadata() + val entry = PlaybackCenter.currentEntry ?: return super.getMediaMetadata() + return MediaMetadata.Builder() + .setTitle(entry.title) + .setArtist(entry.uploader) + .setArtworkUri(entry.thumbnailUrl.takeIf { it.isNotBlank() }?.let(Uri::parse)) + .build() + } + + override fun setPlayWhenReady(playWhenReady: Boolean) { + if (videoRequestPending) { + pendingVideo.setPlayWhenReady(playWhenReady) + refreshSessionState() + PlaybackCenter.notify { it.onPlaybackStateChanged(false) } + } else { + super.setPlayWhenReady(playWhenReady) + } + } + + override fun play() = setPlayWhenReady(true) + override fun pause() = setPlayWhenReady(false) + + /** Emit actual logical-state changes synchronously; never poll or fake READY. */ + fun refreshSessionState() { + refreshAvailableCommands() + val ready = playWhenReady + val state = playbackState + val playing = isPlaying + val metadata = mediaMetadata + val readyChanged = ready != reportedPlayWhenReady + val stateChanged = state != reportedPlaybackState + val playingChanged = playing != reportedIsPlaying + val metadataChanged = metadata != reportedMetadata + if (!readyChanged && !stateChanged && !playingChanged && !metadataChanged) return + reportedPlayWhenReady = ready + reportedPlaybackState = state + reportedIsPlaying = playing + reportedMetadata = metadata + val flags = androidx.media3.common.FlagSet.Builder() + if (readyChanged) flags.add(Player.EVENT_PLAY_WHEN_READY_CHANGED) + if (stateChanged) flags.add(Player.EVENT_PLAYBACK_STATE_CHANGED) + if (playingChanged) flags.add(Player.EVENT_IS_PLAYING_CHANGED) + if (metadataChanged) flags.add(Player.EVENT_MEDIA_METADATA_CHANGED) + val events = Player.Events(flags.build()) + val snapshot = listenerBridges.keys.toList() + snapshot.forEach { listener -> + if (readyChanged) listener.onPlayWhenReadyChanged(ready, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST) + if (stateChanged) listener.onPlaybackStateChanged(state) + if (playingChanged) listener.onIsPlayingChanged(playing) + if (metadataChanged) listener.onMediaMetadataChanged(metadata) + } + snapshot.forEach { it.onEvents(this, events) } + } + + override fun addListener(listener: Player.Listener) { + if (listenerBridges.containsKey(listener)) return + // Java-default callbacks require explicit forwarding, not Kotlin `by` delegation. + val bridge = object : PlayerListenerBridge(listener) { + override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { + listener.onPlayWhenReadyChanged(this@QueueForwardingPlayer.playWhenReady, reason) + } + + override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { + listener.onPlayerStateChanged(this@QueueForwardingPlayer.playWhenReady, this@QueueForwardingPlayer.playbackState) + } + + override fun onPlaybackStateChanged(playbackState: Int) { + listener.onPlaybackStateChanged(this@QueueForwardingPlayer.playbackState) + } + + override fun onIsPlayingChanged(isPlaying: Boolean) { + listener.onIsPlayingChanged(this@QueueForwardingPlayer.isPlaying) + } + + override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { + listener.onMediaMetadataChanged(this@QueueForwardingPlayer.mediaMetadata) + } + + override fun onAvailableCommandsChanged(commands: Player.Commands) { + val current = this@QueueForwardingPlayer.availableCommands + lastCommands = current + listener.onAvailableCommandsChanged(current) + } + + override fun onEvents(player: Player, events: Player.Events) { + refreshSessionState() + listener.onEvents(this@QueueForwardingPlayer, events) + } + } + listenerBridges[listener] = bridge + super.addListener(bridge) + } + + override fun removeListener(listener: Player.Listener) { + val bridge = listenerBridges.remove(listener) ?: return + super.removeListener(bridge) + } + private fun queueHasNext(): Boolean = when (PlaybackCenter.mode) { - Mode.VIDEO, Mode.AUDIO -> - PlaybackCenter.queueIndex < PlaybackCenter.queue.size - 1 + Mode.VIDEO -> QueueNavigationPolicy.hasNext(PlaybackCenter.queueIndex, PlaybackCenter.queue.size) + Mode.AUDIO -> super.hasNextMediaItem() + Mode.NONE -> false + } + + private fun queueHasPrevious(): Boolean = when (PlaybackCenter.mode) { + Mode.VIDEO -> QueueNavigationPolicy.hasPrevious(PlaybackCenter.queueIndex, PlaybackCenter.queue.size) + Mode.AUDIO -> super.hasPreviousMediaItem() Mode.NONE -> false } override fun isCommandAvailable(command: Int): Boolean = when (command) { - Player.COMMAND_SEEK_TO_NEXT, Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> - queueHasNext() || super.isCommandAvailable(command) + Player.COMMAND_SEEK_TO_NEXT, Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> queueHasNext() + Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> queueHasPrevious() + Player.COMMAND_SEEK_TO_PREVIOUS -> when (PlaybackCenter.mode) { + Mode.VIDEO -> QueueNavigationPolicy.hasCurrent(PlaybackCenter.queueIndex, PlaybackCenter.queue.size) + Mode.AUDIO -> super.isCommandAvailable(command) + Mode.NONE -> false + } else -> super.isCommandAvailable(command) } + override fun getAvailableCommands(): Player.Commands { + val commands = super.getAvailableCommands().buildUpon() + for (command in listOf( + Player.COMMAND_SEEK_TO_NEXT, Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, + Player.COMMAND_SEEK_TO_PREVIOUS, Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM + )) { + if (isCommandAvailable(command)) commands.add(command) else commands.remove(command) + } + return commands.build() + } + + fun refreshAvailableCommands() { + val current = availableCommands + if (current == lastCommands) return + lastCommands = current + val snapshot = listenerBridges.keys.toList() + snapshot.forEach { it.onAvailableCommandsChanged(current) } + val events = Player.Events(androidx.media3.common.FlagSet.Builder() + .add(Player.EVENT_AVAILABLE_COMMANDS_CHANGED).build()) + snapshot.forEach { it.onEvents(this, events) } + } + override fun hasNextMediaItem(): Boolean = - queueHasNext() || super.hasNextMediaItem() + queueHasNext() + + override fun hasPreviousMediaItem(): Boolean = + queueHasPrevious() override fun seekToNext() { PlaybackCenter.next() @@ -1579,29 +1949,93 @@ object PlaybackCenter { /** The wrapper for the currently attached PlayerView + the raw player it wraps. */ private var forwardingPlayer: QueueForwardingPlayer? = null private var forwardingFor: ExoPlayer? = null + /** ExoPlayer has one video output; only this view may own it at a time. */ + private var activePlayerView: PlayerView? = null @OptIn(UnstableApi::class) fun attachView(view: PlayerView) { - val raw = playerRef ?: return - val fwd = forwardingPlayer?.takeIf { forwardingFor === raw } - ?: QueueForwardingPlayer(raw).also { - forwardingPlayer = it - forwardingFor = raw - } - view.player = fwd + val outputPlayer = player + if (activePlayerView === view && view.player === outputPlayer) return + // Rendering does not need queue-command interception/listener bridging. + // Queue-aware notification controls continue to use sessionPlayer. + videoFrameSeen = false + PlayerView.switchTargetView(outputPlayer, activePlayerView, view) + activePlayerView = view + android.util.Log.i("SparkTubeVideo", "attach ${view.javaClass.simpleName} output=${view.videoSurfaceView?.javaClass?.simpleName}") + startFrameWatch() } fun detachView(view: PlayerView) { - if (view.player === forwardingPlayer) { - view.player = null + if (activePlayerView !== view) return + activePlayerView = null + if (view.player === playerRef) view.player = null + frameWatchJob?.cancel() + } + + /** READY/isPlaying can be audio-only. Measure actual first-frame delivery on a visible output. */ + private fun startFrameWatch() { + frameWatchJob?.cancel() + if (mode != Mode.VIDEO || audioOnlyMode || videoFrameSeen) return + frameWatchJob = scope.launch { + var visibleWaitMs = 0L + var last = SystemClock.elapsedRealtime() + while (isActive) { + delay(500) + val now = SystemClock.elapsedRealtime() + val elapsed = (now - last).coerceAtMost(1_000L) + last = now + val p = playerRef ?: return@launch + val view = activePlayerView + if (videoFrameSeen || mode != Mode.VIDEO || audioOnlyMode) return@launch + // No recovery while backgrounded, hidden, paused, or waiting for network startup. + if (view == null || !view.isShown || !view.isAttachedToWindow || + view.windowVisibility != android.view.View.VISIBLE || + !p.playWhenReady || p.playbackState != Player.STATE_READY) continue + visibleWaitMs += elapsed + when (VideoFramePolicy.action(visibleWaitMs, videoFrameSeen, frameRecoveryStep)) { + VideoFramePolicy.Action.WAIT -> Unit + VideoFramePolicy.Action.REBIND_OUTPUT -> { + frameRecoveryStep++ + visibleWaitMs = 0L + android.util.Log.w("SparkTubeVideo", "No first frame: rebinding output; videoSelected=${p.currentTracks.isTypeSelected(C.TRACK_TYPE_VIDEO)} size=${p.videoSize.width}x${p.videoSize.height}") + // A view reference alone doesn't prove a live renderer output. + view.player = null + view.player = p + } + VideoFramePolicy.Action.RETRY_SOURCE -> { + frameRecoveryStep++ + val c = catalog + if (c != null) { + android.util.Log.w("SparkTubeVideo", "No first frame after rebind: retrying progressive source") + dashBlocked = true + usedFallback = c.muxed.isNotEmpty() + rebuildSource(keepPosition = true) + return@launch + } + visibleWaitMs = 0L + } + VideoFramePolicy.Action.SHOW_ERROR -> { + android.util.Log.e("SparkTubeVideo", "Video output failed after bounded recovery") + p.pause() + notify { it.onError("Video could not render on this device. Tap Retry or choose a different quality.") } + return@launch + } + } + } } } fun release() { + clearSleepTimer() + frameWatchJob?.cancel() resolveJob?.cancel() prefetchJob?.cancel() watchdogJob?.cancel() scope.cancel() + activePlayerView?.let { view -> + if (view.player === playerRef) view.player = null + } + activePlayerView = null forwardingPlayer = null forwardingFor = null playerRef?.release() diff --git a/app/src/main/java/com/sparktube/app/playback/PlaybackService.kt b/app/src/main/java/com/sparktube/app/playback/PlaybackService.kt index cd884ac..9a1f090 100644 --- a/app/src/main/java/com/sparktube/app/playback/PlaybackService.kt +++ b/app/src/main/java/com/sparktube/app/playback/PlaybackService.kt @@ -3,72 +3,54 @@ package com.sparktube.app.playback import android.app.PendingIntent import android.content.Intent import android.os.Bundle +import android.view.KeyEvent import androidx.annotation.OptIn +import androidx.core.content.IntentCompat import androidx.media3.common.util.UnstableApi -import androidx.media3.session.CommandButton +import androidx.media3.session.MediaConstants import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import androidx.media3.session.SessionCommand import androidx.media3.session.SessionResult import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture -import com.sparktube.app.R import com.sparktube.app.ui.MainActivity import com.sparktube.app.ui.music.NowPlayingActivity /** - * Keeps playback alive in the background and shows the media notification: - * seek bar + previous / play-pause / next controls (native, straight from - * the player playlist) plus a favorite custom button. Tapping the - * notification opens the Now Playing screen for music and the main app - * otherwise. + * Background playback with native previous / play-pause / next controls. + * The session uses the app-queue-aware player, including for videos whose + * raw ExoPlayer timeline contains only the current item. Next changes tracks; + * it is not a ten-second seek. Favorites remain available inside the app. */ @OptIn(UnstableApi::class) class PlaybackService : MediaSessionService() { private var mediaSession: MediaSession? = null + private val playbackListener = object : PlaybackCenter.Listener { + override fun onItemChanged(entry: QueueEntry?) { + updateSessionActivity() + } + } override fun onCreate() { super.onCreate() PlaybackCenter.init(this) - mediaSession = MediaSession.Builder(this, PlaybackCenter.player) + mediaSession = MediaSession.Builder(this, PlaybackCenter.sessionPlayer) .setCallback(SessionCallback()) .setSessionActivity(sessionActivity()) + // Android 13+ keeps the transport slots instead of filling a + // missing Next slot with a custom favorite/heart action. + .setSessionExtras(Bundle().apply { + putBoolean(MediaConstants.EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV, true) + putBoolean(MediaConstants.EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT, true) + }) + .setCustomLayout(emptyList()) .build() - updateCustomLayout() - PlaybackCenter.addListener(object : PlaybackCenter.Listener { - override fun onFavoriteChanged(url: String, isFavorite: Boolean) { - updateCustomLayout() - } - - override fun onItemChanged(entry: QueueEntry?) { - updateCustomLayout() - updateSessionActivity() - } - }) - } - - private fun updateCustomLayout() { - val session = mediaSession ?: return - val isFavorite = PlaybackCenter.isCurrentFavorite() - // Previous / next / seekbar come from the player itself (real - // playlist), so only the favourite toggle is a custom button. - val buttons = listOf( - CommandButton.Builder() - .setDisplayName(if (isFavorite) "Unfavorite" else "Favorite") - .setSessionCommand(SessionCommand(ACTION_FAV, Bundle.EMPTY)) - .setIconResId( - if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border - ) - .build() - ) - session.setCustomLayout(buttons) + PlaybackCenter.addListener(playbackListener) } - /** - * What tapping the notification / system media controls opens: the Now - * Playing screen while music is active, the main app otherwise. - */ + /** Opens the music view for audio, or the main app for video playback. */ private fun sessionActivity(): PendingIntent { val target = if (PlaybackCenter.mode == PlaybackCenter.Mode.AUDIO) { NowPlayingActivity::class.java @@ -92,17 +74,39 @@ class PlaybackService : MediaSessionService() { mediaSession override fun onDestroy() { + PlaybackCenter.removeListener(playbackListener) mediaSession?.release() mediaSession = null super.onDestroy() } private inner class SessionCallback : MediaSession.Callback { + override fun onMediaButtonEvent( + session: MediaSession, + controllerInfo: MediaSession.ControllerInfo, + intent: Intent + ): Boolean { + if (intent.action != Intent.ACTION_MEDIA_BUTTON) return false + val event = IntentCompat.getParcelableExtra(intent, Intent.EXTRA_KEY_EVENT, KeyEvent::class.java) + ?: return false + // Media3 1.3.1 otherwise waits for a possible double-tap, and a + // second quick notification tap can become Next. Only bypass that + // gesture recognizer for the notification controller, not headsets. + return NotificationTransport.handle( + session.player, + session.isMediaNotificationController(controllerInfo), + event.action, + event.keyCode, + event.repeatCount + ) + } override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): MediaSession.ConnectionResult { + // Retain the existing custom command API for external controllers; + // the visible notification uses only native transport controls. val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() .add(SessionCommand(ACTION_FAV, Bundle.EMPTY)) .add(SessionCommand(ACTION_PREV, Bundle.EMPTY)) diff --git a/app/src/main/java/com/sparktube/app/playback/PlayerListenerBridge.java b/app/src/main/java/com/sparktube/app/playback/PlayerListenerBridge.java new file mode 100644 index 0000000..2d3574a --- /dev/null +++ b/app/src/main/java/com/sparktube/app/playback/PlayerListenerBridge.java @@ -0,0 +1,164 @@ +package com.sparktube.app.playback; + +/** + * Explicit forwarding is essential: Kotlin `Player.Listener by delegate` inherits + * Java default no-ops instead of forwarding them with our current Kotlin compiler. + * That swallowed onRenderedFirstFrame/onTracksChanged and left PlayerView's shutter black. + * Keep every Player.Listener callback here; PlayerListenerBridgeTest checks API coverage. + */ +@SuppressWarnings("deprecation") +class PlayerListenerBridge implements androidx.media3.common.Player.Listener { + private final androidx.media3.common.Player.Listener delegate; + + PlayerListenerBridge(androidx.media3.common.Player.Listener delegate) { + this.delegate = delegate; + } + + @Override public void onEvents(androidx.media3.common.Player arg0, androidx.media3.common.Player.Events arg1) { + delegate.onEvents(arg0, arg1); + } + + @Override public void onTimelineChanged(androidx.media3.common.Timeline arg0, int arg1) { + delegate.onTimelineChanged(arg0, arg1); + } + + @Override public void onMediaItemTransition(androidx.media3.common.MediaItem arg0, int arg1) { + delegate.onMediaItemTransition(arg0, arg1); + } + + @Override public void onTracksChanged(androidx.media3.common.Tracks arg0) { + delegate.onTracksChanged(arg0); + } + + @Override public void onMediaMetadataChanged(androidx.media3.common.MediaMetadata arg0) { + delegate.onMediaMetadataChanged(arg0); + } + + @Override public void onPlaylistMetadataChanged(androidx.media3.common.MediaMetadata arg0) { + delegate.onPlaylistMetadataChanged(arg0); + } + + @Override public void onIsLoadingChanged(boolean arg0) { + delegate.onIsLoadingChanged(arg0); + } + + @Override public void onLoadingChanged(boolean arg0) { + delegate.onLoadingChanged(arg0); + } + + @Override public void onAvailableCommandsChanged(androidx.media3.common.Player.Commands arg0) { + delegate.onAvailableCommandsChanged(arg0); + } + + @Override public void onTrackSelectionParametersChanged(androidx.media3.common.TrackSelectionParameters arg0) { + delegate.onTrackSelectionParametersChanged(arg0); + } + + @Override public void onPlayerStateChanged(boolean arg0, int arg1) { + delegate.onPlayerStateChanged(arg0, arg1); + } + + @Override public void onPlaybackStateChanged(int arg0) { + delegate.onPlaybackStateChanged(arg0); + } + + @Override public void onPlayWhenReadyChanged(boolean arg0, int arg1) { + delegate.onPlayWhenReadyChanged(arg0, arg1); + } + + @Override public void onPlaybackSuppressionReasonChanged(int arg0) { + delegate.onPlaybackSuppressionReasonChanged(arg0); + } + + @Override public void onIsPlayingChanged(boolean arg0) { + delegate.onIsPlayingChanged(arg0); + } + + @Override public void onRepeatModeChanged(int arg0) { + delegate.onRepeatModeChanged(arg0); + } + + @Override public void onShuffleModeEnabledChanged(boolean arg0) { + delegate.onShuffleModeEnabledChanged(arg0); + } + + @Override public void onPlayerError(androidx.media3.common.PlaybackException arg0) { + delegate.onPlayerError(arg0); + } + + @Override public void onPlayerErrorChanged(androidx.media3.common.PlaybackException arg0) { + delegate.onPlayerErrorChanged(arg0); + } + + @Override public void onPositionDiscontinuity(int arg0) { + delegate.onPositionDiscontinuity(arg0); + } + + @Override public void onPositionDiscontinuity(androidx.media3.common.Player.PositionInfo arg0, androidx.media3.common.Player.PositionInfo arg1, int arg2) { + delegate.onPositionDiscontinuity(arg0, arg1, arg2); + } + + @Override public void onPlaybackParametersChanged(androidx.media3.common.PlaybackParameters arg0) { + delegate.onPlaybackParametersChanged(arg0); + } + + @Override public void onSeekBackIncrementChanged(long arg0) { + delegate.onSeekBackIncrementChanged(arg0); + } + + @Override public void onSeekForwardIncrementChanged(long arg0) { + delegate.onSeekForwardIncrementChanged(arg0); + } + + @Override public void onMaxSeekToPreviousPositionChanged(long arg0) { + delegate.onMaxSeekToPreviousPositionChanged(arg0); + } + + @Override public void onAudioSessionIdChanged(int arg0) { + delegate.onAudioSessionIdChanged(arg0); + } + + @Override public void onAudioAttributesChanged(androidx.media3.common.AudioAttributes arg0) { + delegate.onAudioAttributesChanged(arg0); + } + + @Override public void onVolumeChanged(float arg0) { + delegate.onVolumeChanged(arg0); + } + + @Override public void onSkipSilenceEnabledChanged(boolean arg0) { + delegate.onSkipSilenceEnabledChanged(arg0); + } + + @Override public void onDeviceInfoChanged(androidx.media3.common.DeviceInfo arg0) { + delegate.onDeviceInfoChanged(arg0); + } + + @Override public void onDeviceVolumeChanged(int arg0, boolean arg1) { + delegate.onDeviceVolumeChanged(arg0, arg1); + } + + @Override public void onVideoSizeChanged(androidx.media3.common.VideoSize arg0) { + delegate.onVideoSizeChanged(arg0); + } + + @Override public void onSurfaceSizeChanged(int arg0, int arg1) { + delegate.onSurfaceSizeChanged(arg0, arg1); + } + + @Override public void onRenderedFirstFrame() { + delegate.onRenderedFirstFrame(); + } + + @Override public void onCues(java.util.List arg0) { + delegate.onCues(arg0); + } + + @Override public void onCues(androidx.media3.common.text.CueGroup arg0) { + delegate.onCues(arg0); + } + + @Override public void onMetadata(androidx.media3.common.Metadata arg0) { + delegate.onMetadata(arg0); + } +} diff --git a/app/src/main/java/com/sparktube/app/playback/QueueNavigationPolicy.kt b/app/src/main/java/com/sparktube/app/playback/QueueNavigationPolicy.kt new file mode 100644 index 0000000..2b79080 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/playback/QueueNavigationPolicy.kt @@ -0,0 +1,8 @@ +package com.sparktube.app.playback + +/** Video app-queue boundaries, independent of Android and stream resolution. */ +internal object QueueNavigationPolicy { + fun hasCurrent(index: Int, size: Int): Boolean = size > 0 && index >= 0 && index < size + fun hasNext(index: Int, size: Int): Boolean = hasCurrent(index, size) && index < size - 1 + fun hasPrevious(index: Int, size: Int): Boolean = hasCurrent(index, size) && index > 0 +} diff --git a/app/src/main/java/com/sparktube/app/playback/SleepTimerState.kt b/app/src/main/java/com/sparktube/app/playback/SleepTimerState.kt new file mode 100644 index 0000000..7196101 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/playback/SleepTimerState.kt @@ -0,0 +1,57 @@ +package com.sparktube.app.playback + +/** Pure state for playback sleep timers. Time values are monotonic milliseconds supplied by the caller. */ +class SleepTimerState { + enum class Mode { OFF, DURATION, END_OF_ITEM } + + var mode: Mode = Mode.OFF + private set + + /** Monotonic deadline for [Mode.DURATION], otherwise null. */ + var deadlineMs: Long? = null + private set + + private var endOfItemMediaId: String? = null + + /** Starts or replaces a duration timer. Valid minutes are positive Int values. */ + fun setDuration(minutes: Int, nowMs: Long) { + require(minutes > 0) { "minutes must be positive" } + val durationMs = minutes.toLong() * MILLIS_PER_MINUTE + // Saturation is defensive for unusual caller-supplied clocks; normal elapsedRealtime values fit. + deadlineMs = if (nowMs > Long.MAX_VALUE - durationMs) Long.MAX_VALUE else nowMs + durationMs + mode = Mode.DURATION + endOfItemMediaId = null + } + + /** Stops at the end of the item whose ID is captured now (including automatic repeat). */ + fun setEndOfItem(mediaId: String?) { + mode = Mode.END_OF_ITEM + deadlineMs = null + endOfItemMediaId = mediaId + } + + fun clear() { + mode = Mode.OFF + deadlineMs = null + endOfItemMediaId = null + } + + /** Remaining duration, clamped to zero; null unless a duration timer is active. */ + fun remainingMs(nowMs: Long): Long? = + if (mode == Mode.DURATION) ((deadlineMs!! - nowMs).coerceAtLeast(0L)) else null + + fun isExpired(nowMs: Long): Boolean = + mode == Mode.DURATION && nowMs >= deadlineMs!! + + /** True only when the item reaching its end is the item captured by END_OF_ITEM. */ + fun shouldStopAtEnd(mediaId: String?): Boolean = + mode == Mode.END_OF_ITEM && mediaId == endOfItemMediaId + + /** Stops before an automatic transition (including repeat) out of the captured item. */ + fun shouldStopOnTransition(previousMediaId: String?, automatic: Boolean): Boolean = + automatic && mode == Mode.END_OF_ITEM && previousMediaId == endOfItemMediaId + + private companion object { + const val MILLIS_PER_MINUTE = 60_000L + } +} diff --git a/app/src/main/java/com/sparktube/app/playback/VideoFramePolicy.kt b/app/src/main/java/com/sparktube/app/playback/VideoFramePolicy.kt new file mode 100644 index 0000000..a8d0a98 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/playback/VideoFramePolicy.kt @@ -0,0 +1,16 @@ +package com.sparktube.app.playback + +/** Playback can be READY because of audio alone; only a rendered frame proves video output. */ +internal object VideoFramePolicy { + const val TIMEOUT_MS = 8_000L + enum class Action { WAIT, REBIND_OUTPUT, RETRY_SOURCE, SHOW_ERROR } + + fun action(visibleWaitMs: Long, firstFrameSeen: Boolean, recoveryStep: Int): Action { + if (firstFrameSeen || visibleWaitMs < TIMEOUT_MS) return Action.WAIT + return when (recoveryStep) { + 0 -> Action.REBIND_OUTPUT + 1 -> Action.RETRY_SOURCE + else -> Action.SHOW_ERROR + } + } +} diff --git a/app/src/main/java/com/sparktube/app/ui/MainActivity.kt b/app/src/main/java/com/sparktube/app/ui/MainActivity.kt index 554407e..c535c99 100644 --- a/app/src/main/java/com/sparktube/app/ui/MainActivity.kt +++ b/app/src/main/java/com/sparktube/app/ui/MainActivity.kt @@ -99,6 +99,19 @@ class MainActivity : AppCompatActivity() { } applySelection() + if (intent.getBooleanExtra(EXTRA_OPEN_DOWNLOADS, false)) showDownloads() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + if (intent.getBooleanExtra(EXTRA_OPEN_DOWNLOADS, false)) showDownloads() + } + + private fun showDownloads() { + select(R.id.navLibrary) + supportFragmentManager.executePendingTransactions() + (fragments[R.id.navLibrary] as? LibraryFragment)?.showDownloadsTab() } override fun onSaveInstanceState(outState: Bundle) { @@ -278,6 +291,13 @@ class MainActivity : AppCompatActivity() { } companion object { + private const val EXTRA_OPEN_DOWNLOADS = "open_downloads" + fun openDownloads(context: android.content.Context) { + context.startActivity(Intent(context, MainActivity::class.java).apply { + putExtra(EXTRA_OPEN_DOWNLOADS, true) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP) + }) + } private const val STATE_SELECTED = "selected_nav" private const val REQ_NOTIFICATIONS = 4711 private val NAV_IDS = intArrayOf(R.id.navHome, R.id.navMusic, R.id.navLibrary, R.id.navMenu) diff --git a/app/src/main/java/com/sparktube/app/ui/common/DownloadAdapter.kt b/app/src/main/java/com/sparktube/app/ui/common/DownloadAdapter.kt index 830c09e..4cd8121 100644 --- a/app/src/main/java/com/sparktube/app/ui/common/DownloadAdapter.kt +++ b/app/src/main/java/com/sparktube/app/ui/common/DownloadAdapter.kt @@ -1,62 +1,141 @@ package com.sparktube.app.ui.common import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup -import androidx.core.content.ContextCompat +import androidx.appcompat.widget.PopupMenu import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import coil.load import com.sparktube.app.R import com.sparktube.app.data.DownloadRecord import com.sparktube.app.databinding.ItemDownloadBinding import com.sparktube.app.download.DownloadCenter +import com.sparktube.app.download.DownloadSharing import com.sparktube.app.util.Thumbs /** Download entries on the Library > Downloads tab. */ class DownloadAdapter( private val onClick: (DownloadRecord) -> Unit, - private val onLongClick: (DownloadRecord) -> Unit + private val onLongClick: (DownloadRecord) -> Unit, + private val onPause: ((DownloadRecord) -> Unit)? = null, + private val audioStyle: Boolean = false ) : ListAdapter(DIFF) { - companion object { private val DIFF = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(a: DownloadRecord, b: DownloadRecord): Boolean = a.videoId == b.videoId && a.type == b.type && a.quality == b.quality - override fun areContentsTheSame(a: DownloadRecord, b: DownloadRecord): Boolean = - a.status == b.status && a.title == b.title + a.status == b.status && a.title == b.title && a.filePaths == b.filePaths } } class VH(val binding: ItemDownloadBinding) : RecyclerView.ViewHolder(binding.root) + /** Runtime metrics are not persisted in DownloadRecord or compared by DiffUtil. */ + fun refreshLiveMetrics() { notifyItemRangeChanged(0, itemCount) } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH = VH(ItemDownloadBinding.inflate(LayoutInflater.from(parent.context), parent, false)) override fun onBindViewHolder(holder: VH, position: Int) { val item = getItem(position) val b = holder.binding - + val context = b.root.context + if (audioStyle) { + val density = context.resources.displayMetrics.density + val imageSize = (60 * density).toInt() + b.thumb.layoutParams = b.thumb.layoutParams.apply { + width = imageSize + height = imageSize + } + b.title.maxLines = 1 + b.title.textSize = 15f + b.meta.maxLines = 1 + b.root.setPadding((8 * density).toInt(), (8 * density).toInt(), (4 * density).toInt(), (8 * density).toInt()) + } b.title.text = item.title val typeLabel = when (item.type) { - DownloadCenter.TYPE_AUDIO -> b.root.context.getString(R.string.download_type_audio) - DownloadCenter.TYPE_AV -> b.root.context.getString(R.string.download_type_av) - else -> b.root.context.getString(R.string.download_type_video) + DownloadCenter.TYPE_AUDIO -> context.getString(R.string.download_type_audio) + DownloadCenter.TYPE_AV -> context.getString(R.string.download_type_av) + else -> context.getString(R.string.download_type_video) } val statusLabel = when (item.status) { - DownloadCenter.STATUS_DONE -> b.root.context.getString(R.string.download_status_done) - DownloadCenter.STATUS_RUNNING -> b.root.context.getString(R.string.download_status_running) - DownloadCenter.STATUS_FAILED -> b.root.context.getString(R.string.download_status_failed) - else -> b.root.context.getString(R.string.download_status_pending) + DownloadCenter.STATUS_DONE -> context.getString(R.string.download_status_done) + DownloadCenter.STATUS_RUNNING -> context.getString(R.string.download_status_running) + DownloadCenter.STATUS_FAILED -> context.getString(R.string.download_status_failed) + DownloadCenter.STATUS_PAUSED -> context.getString(R.string.download_paused) + else -> context.getString(R.string.download_status_pending) + } + b.meta.text = if (audioStyle) item.uploader.ifBlank { typeLabel } + else "$typeLabel • ${item.quality} • $statusLabel" + val active = item.status == DownloadCenter.STATUS_PENDING || + item.status == DownloadCenter.STATUS_RUNNING || item.status == DownloadCenter.STATUS_PAUSED + val paused = item.status == DownloadCenter.STATUS_PAUSED + b.pauseButton.visibility = if (active) View.VISIBLE else View.GONE + b.pauseButton.text = context.getString(if (paused) R.string.download_resume else R.string.download_pause) + b.pauseButton.setOnClickListener { + onPause?.invoke(item) ?: DownloadCenter.togglePause(context, item) } - b.meta.text = "$typeLabel • ${item.quality} • $statusLabel" - Thumbs.load(b.thumb, item.thumbnailUrl) - b.root.setOnClickListener { if (item.status == DownloadCenter.STATUS_DONE) onClick(item) } - b.root.setOnLongClickListener { - onLongClick(item) - true + // Exactly one snapshot per bind; getters must not change the sampling period. + val metrics = DownloadCenter.metrics(item) + val total = metrics.totalBytes + val downloaded = DownloadMetricFormat.megabytes(metrics.downloadedBytes) + val sizeText = context.getString(R.string.download_size_progress, downloaded, + total?.let(DownloadMetricFormat::megabytes) ?: context.getString(R.string.download_total_unknown)) + b.progress.visibility = if (active) View.VISIBLE else View.GONE + b.progress.isIndeterminate = active && !paused && total == null + b.progress.progress = if (total != null && total > 0L) { + (metrics.downloadedBytes.toDouble() / total.toDouble() * 100.0).toInt().coerceIn(0, 100) + } else 0 + b.speed.text = when { + paused -> "$sizeText\n${context.getString(R.string.download_paused)}" + active -> { + val eta = metrics.remainingSeconds + val timeText = when { + total == null -> context.getString(R.string.download_time_unknown) + eta != null -> context.getString(R.string.download_remaining, DownloadMetricFormat.duration(eta)) + metrics.bytesPerSecond <= 0.0 && metrics.downloadedBytes > 0L -> context.getString(R.string.download_waiting) + else -> context.getString(R.string.download_estimating) + } + "$sizeText\n${DownloadMetricFormat.rate(metrics.bytesPerSecond)} · $timeText" + } + item.status == DownloadCenter.STATUS_DONE -> downloaded + else -> "" + } + if (audioStyle) { + b.speed.text = when { + active -> { + val percent = if (total != null && total > 0L) "${metrics.progressPercent}% · " else "" + "$percent${b.speed.text}\n${item.quality} • $statusLabel" + } + item.status == DownloadCenter.STATUS_DONE -> listOfNotNull( + item.quality.takeIf { it.isNotBlank() }, + downloaded + ).joinToString(" • ") + else -> statusLabel + } } + b.overflowButton.visibility = if (item.status == DownloadCenter.STATUS_DONE) View.VISIBLE else View.GONE + b.overflowButton.setOnClickListener { anchor -> + PopupMenu(context, anchor).apply { + menu.add(0, 1, 0, R.string.download_open) + menu.add(0, 2, 1, R.string.download_share) + menu.add(0, 3, 2, R.string.download_delete) + setOnMenuItemClickListener { action -> + when (action.itemId) { + 1 -> onClick(item) + 2 -> DownloadSharing.share(context, item) + 3 -> onLongClick(item) // Preserve Library's existing delete confirmation. + } + true + } + show() + } + } + Thumbs.load(b.thumb, item.thumbnailUrl) + b.root.setOnClickListener { if (item.status == DownloadCenter.STATUS_DONE) onClick(item) } + b.root.setOnLongClickListener { onLongClick(item); true } } } diff --git a/app/src/main/java/com/sparktube/app/ui/common/DownloadMetricFormat.kt b/app/src/main/java/com/sparktube/app/ui/common/DownloadMetricFormat.kt new file mode 100644 index 0000000..7e03cb5 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/common/DownloadMetricFormat.kt @@ -0,0 +1,30 @@ +package com.sparktube.app.ui.common + +import java.util.Locale + +/** Decimal MB (1,000,000 bytes), never megabits or mislabeled binary MiB. */ +internal object DownloadMetricFormat { + fun megabytes(bytes: Long): String { + val value = bytes.coerceAtLeast(0L) + if (value in 1L..4_999L) return "<0.01 MB" + return String.format(Locale.US, "%.2f MB", value.toDouble() / 1_000_000.0) + } + + fun rate(bytesPerSecond: Double): String { + val value = bytesPerSecond.takeIf { it.isFinite() && it > 0.0 } ?: 0.0 + if (value > 0.0 && value < 5_000.0) return "<0.01 MB/s" + return String.format(Locale.US, "%.2f MB/s", value / 1_000_000.0) + } + + fun duration(seconds: Long): String { + val value = seconds.coerceAtLeast(0L) + val hours = value / 3_600L + val minutes = value % 3_600L / 60L + val remainder = value % 60L + return when { + hours > 0 -> "$hours h $minutes min" + minutes > 0 -> "$minutes min $remainder s" + else -> "$remainder s" + } + } +} diff --git a/app/src/main/java/com/sparktube/app/ui/common/VideoAdapter.kt b/app/src/main/java/com/sparktube/app/ui/common/VideoAdapter.kt index 78334e6..52a5125 100644 --- a/app/src/main/java/com/sparktube/app/ui/common/VideoAdapter.kt +++ b/app/src/main/java/com/sparktube/app/ui/common/VideoAdapter.kt @@ -1,19 +1,22 @@ package com.sparktube.app.ui.common +import android.content.Intent import android.view.LayoutInflater import android.view.ViewGroup -import androidx.core.content.ContextCompat +import android.widget.Toast +import androidx.appcompat.widget.PopupMenu import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import coil.load import com.sparktube.app.R +import com.sparktube.app.data.LocalStore import com.sparktube.app.databinding.ItemVideoBinding import com.sparktube.app.util.Thumbs class VideoAdapter( private val onClick: (VideoUiModel) -> Unit, - private val onLongClick: ((VideoUiModel) -> Boolean)? = null + private val onLongClick: ((VideoUiModel) -> Boolean)? = null, + private val onDownload: ((VideoUiModel) -> Unit)? = null ) : ListAdapter(DIFF) { companion object { @@ -49,6 +52,43 @@ class VideoAdapter( Thumbs.load(b.thumbnail, item.thumbnailUrl) Thumbs.load(b.avatar, item.uploaderAvatarUrl) + b.overflowButton.contentDescription = b.root.context.getString(R.string.video_more_options, item.title) + b.overflowButton.setOnClickListener { anchor -> + val context = anchor.context + PopupMenu(context, anchor).apply { + menu.add(0, 1, 0, R.string.cd_play) + menu.add(0, 5, 1, R.string.action_download) + // Library/playlist screens own removal and refresh of their lists. + // Expose those existing callbacks only after a deliberate menu choice. + if (onLongClick != null) { + menu.add(0, 4, 4, R.string.video_remove_from_list) + } else { + menu.add(0, 2, 2, if (LocalStore.isFavorite(context, item.url)) + R.string.video_remove_favorite else R.string.action_favorite) + } + menu.add(0, 3, 3, R.string.action_share) + setOnMenuItemClickListener { action -> + when (action.itemId) { + 1 -> onClick(item) + 5 -> if (onDownload != null) onDownload.invoke(item) + else VideoDownloadAction.start(context, item) + 2 -> { + val added = LocalStore.toggleFavorite(context, item.toEntry()) + Toast.makeText(context, if (added) R.string.added_to_favorites + else R.string.removed_from_favorites, Toast.LENGTH_SHORT).show() + } + 3 -> context.startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, item.url) + }, context.getString(R.string.share_via))) + 4 -> onLongClick?.invoke(item) + else -> return@setOnMenuItemClickListener false + } + true + } + show() + } + } b.root.setOnClickListener { onClick(item) } b.root.setOnLongClickListener { onLongClick?.invoke(item) ?: false diff --git a/app/src/main/java/com/sparktube/app/ui/common/VideoDownloadAction.kt b/app/src/main/java/com/sparktube/app/ui/common/VideoDownloadAction.kt new file mode 100644 index 0000000..c01e980 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/common/VideoDownloadAction.kt @@ -0,0 +1,61 @@ +package com.sparktube.app.ui.common + +import android.content.Context +import android.content.ContextWrapper +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.lifecycle.lifecycleScope +import com.sparktube.app.R +import com.sparktube.app.data.YtRepository +import com.sparktube.app.download.DownloadCenter +import com.sparktube.app.playback.StreamCatalog +import com.sparktube.app.ui.MainActivity +import com.sparktube.app.ui.player.DownloadSheet +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.launch +import java.util.WeakHashMap + +/** Starts the existing download picker without changing current playback. */ +internal object VideoDownloadAction { + // Accessed only on the UI thread. Do not retain a finished screen globally. + private val resolving = WeakHashMap>() + + fun start(context: Context, item: VideoUiModel) { + if (DownloadCenter.isDownloaded(context, item.url) || + DownloadCenter.hasActiveDownload(context, item.url)) { + MainActivity.openDownloads(context) + return + } + val activity = activityFrom(context) ?: return + if (activity.isFinishing || activity.isDestroyed) return + val urls = resolving.getOrPut(activity) { mutableSetOf() } + if (!urls.add(item.url)) return + val appContext = context.applicationContext + val entry = item.toQueueEntry() + activity.lifecycleScope.launch { + try { + val catalog = StreamCatalog(YtRepository.streamInfo(item.url)) + if (!activity.isFinishing && !activity.isDestroyed) { + DownloadSheet(activity, entry, catalog).show() + } + } catch (cancelled: CancellationException) { + throw cancelled + } catch (_: Exception) { + Toast.makeText(appContext, R.string.error_no_streams, Toast.LENGTH_SHORT).show() + } finally { + urls.remove(item.url) + if (urls.isEmpty()) resolving.remove(activity) + } + } + } + + private fun activityFrom(context: Context): ComponentActivity? { + var current = context + while (true) { + if (current is ComponentActivity) return current + val base = (current as? ContextWrapper)?.baseContext ?: return null + if (base === current) return null + current = base + } + } +} diff --git a/app/src/main/java/com/sparktube/app/ui/home/HomeFeedCache.kt b/app/src/main/java/com/sparktube/app/ui/home/HomeFeedCache.kt new file mode 100644 index 0000000..3d0ab99 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/home/HomeFeedCache.kt @@ -0,0 +1,104 @@ +package com.sparktube.app.ui.home + +import android.content.Context +import com.sparktube.app.ui.common.VideoUiModel +import org.json.JSONArray +import org.json.JSONObject +import java.io.File + +/** Cache policy and storage for display-only Home cards (watch-page URLs, never resolved stream URLs). */ +internal object HomeFeedCache { + const val MAX_ENTRIES = 60 + const val TTL_MILLIS = 3L * 24 * 60 * 60 * 1000 + private const val VERSION = 1 + private val memory = mutableMapOf() + + data class Snapshot(val savedAtMillis: Long, val items: List) + + /** Pure policy: only a nonempty, recent snapshot is eligible for instant display. */ + fun isUsable(savedAtMillis: Long, nowMillis: Long): Boolean = + savedAtMillis > 0L && nowMillis >= savedAtMillis && nowMillis - savedAtMillis <= TTL_MILLIS + + /** Bounds entries and rejects blank/non-video destinations; URLs remain page URLs from VideoUiModel. */ + fun bounded(items: List): List = items + .asSequence() + .filter { it.url.isNotBlank() && it.title.isNotBlank() } + .distinctBy { it.url } + .take(MAX_ENTRIES) + .toList() + + @Synchronized + fun read(context: Context, country: String, nowMillis: Long = System.currentTimeMillis()): List { + val mem = memory[country] + if (mem != null && isUsable(mem.savedAtMillis, nowMillis) && mem.items.isNotEmpty()) return mem.items + val file = cacheFile(context, country) + val snapshot = runCatching { + val root = JSONObject(file.readText()) + if (root.optInt("version") != VERSION || root.optString("country") != country) return@runCatching null + val saved = root.optLong("savedAt", 0L) + if (!isUsable(saved, nowMillis)) return@runCatching null + val arr = root.optJSONArray("items") ?: return@runCatching null + val result = ArrayList(arr.length().coerceAtMost(MAX_ENTRIES)) + for (i in 0 until arr.length().coerceAtMost(MAX_ENTRIES)) { + val row = arr.optJSONObject(i) ?: continue + result += VideoUiModel( + url = row.optString("url"), title = row.optString("title"), + uploader = row.optString("uploader"), thumbnailUrl = row.optString("thumbnail"), + durationSec = row.optLong("duration"), viewCount = row.optLong("views", -1L), + uploadDate = row.optString("date"), uploaderAvatarUrl = row.optString("avatar") + ) + } + Snapshot(saved, bounded(result)).takeIf { it.items.isNotEmpty() } + }.getOrNull() + if (snapshot == null) { + memory.remove(country) + runCatching { file.delete() } + return emptyList() + } + memory[country] = snapshot + return snapshot.items + } + + @Synchronized + fun write( + context: Context, + country: String, + items: List, + savedAtMillis: Long = System.currentTimeMillis(), + shouldWrite: () -> Boolean = { true } + ) { + // Recheck after acquiring the cache lock, not before waiting behind another writer. + if (!shouldWrite()) return + val safeItems = bounded(items) + if (safeItems.isEmpty() || country.isBlank() || savedAtMillis <= 0L) return + val rows = JSONArray() + safeItems.forEach { model -> + rows.put(JSONObject().put("url", model.url).put("title", model.title) + .put("uploader", model.uploader).put("thumbnail", model.thumbnailUrl) + .put("duration", model.durationSec).put("views", model.viewCount) + .put("date", model.uploadDate).put("avatar", model.uploaderAvatarUrl)) + } + val snapshot = Snapshot(savedAtMillis, safeItems) + memory[country] = snapshot + val file = cacheFile(context, country) + runCatching { + file.parentFile?.mkdirs() + val payload = JSONObject().put("version", VERSION).put("country", country) + .put("savedAt", savedAtMillis).put("items", rows).toString() + val temp = File(file.parentFile, file.name + ".tmp") + temp.writeText(payload) + if (!temp.renameTo(file)) { + file.writeText(payload) + temp.delete() + } + } + } + + @Synchronized + internal fun clearMemoryForTests() = memory.clear() + + private fun cacheFile(context: Context, country: String): File { + val key = country.uppercase().filter { it.isLetterOrDigit() }.take(8).ifBlank { "default" } + return File(File(context.applicationContext.filesDir, "home-feed"), "$key.json") + } +} diff --git a/app/src/main/java/com/sparktube/app/ui/home/HomeFragment.kt b/app/src/main/java/com/sparktube/app/ui/home/HomeFragment.kt index 6a59494..260b48e 100644 --- a/app/src/main/java/com/sparktube/app/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/sparktube/app/ui/home/HomeFragment.kt @@ -10,7 +10,6 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager -import com.sparktube.app.BuildConfig import com.sparktube.app.R import com.sparktube.app.data.Countries import com.sparktube.app.data.RecommendEngine @@ -39,6 +38,8 @@ class HomeFragment : Fragment() { private lateinit var adapter: VideoAdapter private var loadedCountry: String? = null private var loadJob: kotlinx.coroutines.Job? = null + @Volatile private var feedGeneration = 0L + private var visibleCountry: String? = null /** * Elapsed-realtime stamp of the last successful feed load. The feed now @@ -76,23 +77,6 @@ class HomeFragment : Fragment() { ) } - // Test-crash button: verifies the local crash reporter pipeline - // (CrashReporter writes reports into the "SparkTube" folder on the - // device — Download/SparkTube on Android 10+). - // DEBUG BUILDS ONLY — release users never see it. - // Tap → uncaught exception → report file written to the device → - // reopen the app and check Settings → Crash logs to view/share it. - binding.crashTestButton.isVisible = BuildConfig.DEBUG - binding.crashTestButton.setOnClickListener { - CrashReporter.logBreadcrumb("Test crash button tapped on Home") - CrashReporter.logBreadcrumb( - "loaded_country=${loadedCountry ?: ""}" - ) - throw RuntimeException( - "SparkTube test crash — local crash report check" - ) - } - binding.retryButton.setOnClickListener { load() } // One automatic refresh every 5 minutes while the feed is on screen. @@ -141,8 +125,7 @@ class HomeFragment : Fragment() { private fun load() { val country = AppPrefs.countryOrDefault loadedCountry = country - lastLoadedAt = SystemClock.elapsedRealtime() - + val generation = ++feedGeneration loadJob?.cancel() // Capture the binding: a theme change relaunches the activity, which // destroys this view (nulling _binding) while the coroutine may still @@ -154,18 +137,55 @@ class HomeFragment : Fragment() { loadJob = viewLifecycleOwner.lifecycleScope.launch { b.errorView.isVisible = false b.emptyView.isVisible = false - // Skeleton cards instead of a spinner: the feed keeps its shape - // while the personalized list is being fetched. - showSkeleton(b.list, SkeletonAdapter.STYLE_VIDEO, count = 10) + b.list.adapter = adapter + if (visibleCountry != null && visibleCountry != country) { + adapter.submitList(emptyList()) + visibleCountry = null + } + b.swipe.isRefreshing = visibleCountry == country && adapter.currentList.isNotEmpty() try { + // Disk access is off-main. A cached list is installed before any network work. + val cached = withContext(Dispatchers.IO) { HomeFeedCache.read(ctx, country) } + if (!isCurrent(b, country, generation)) return@launch + if (cached.isNotEmpty()) { + adapter.submitList(cached) + visibleCountry = country + } else if (visibleCountry != country || adapter.currentList.isEmpty()) { + showSkeleton(b.list, SkeletonAdapter.STYLE_VIDEO, count = 10) + } val snap = withContext(Dispatchers.IO) { RecommendEngine.snapshot(ctx) } - val items = YtRepository.personalizedFeed(country, snap) + val items = YtRepository.personalizedFeed(country, snap, onInitialItems = { initial -> + val preview = initial.map { it.toUiModel() } + withContext(Dispatchers.Main) { + if (preview.isNotEmpty() && (visibleCountry != country || adapter.currentList.isEmpty()) && isCurrent(b, country, generation)) { + b.list.adapter = adapter + adapter.submitList(preview) + visibleCountry = country + } + } + }) + if (!isCurrent(b, country, generation)) return@launch + val models = items.map { it.toUiModel() } b.list.adapter = adapter - adapter.submitList(items.map { it.toUiModel() }) - b.emptyView.isVisible = items.isEmpty() - if (items.isEmpty()) { + if (models.isNotEmpty()) { + adapter.submitList(models) + visibleCountry = country + lastLoadedAt = SystemClock.elapsedRealtime() + withContext(Dispatchers.IO) { + if (isActive && generation == feedGeneration && AppPrefs.countryOrDefault == country) { + HomeFeedCache.write(ctx, country, models, shouldWrite = { + generation == feedGeneration && AppPrefs.countryOrDefault == country + }) + } + } + } else if (adapter.currentList.isNotEmpty()) { + // An empty network result must not erase a useful saved feed. + b.emptyView.isVisible = false + } + b.emptyView.isVisible = items.isEmpty() && adapter.currentList.isEmpty() + if (items.isEmpty() && adapter.currentList.isEmpty()) { b.errorText.text = getString(R.string.error_no_results) b.errorView.isVisible = true } @@ -173,20 +193,30 @@ class HomeFragment : Fragment() { // A newer load() cancelled this one: not an error. throw e } catch (e: Exception) { + if (!isCurrent(b, country, generation)) return@launch b.list.adapter = adapter - adapter.submitList(emptyList()) - b.errorText.text = Formatters.friendlyException(e) - b.errorView.isVisible = true + if (adapter.currentList.isEmpty()) { + b.errorText.text = Formatters.friendlyException(e) + b.errorView.isVisible = true + } // Mark as "never loaded" so the next return to this screen // retries automatically instead of waiting out the 5 min. lastLoadedAt = 0L } finally { - b.swipe.isRefreshing = false + if (_binding === b && generation == feedGeneration) b.swipe.isRefreshing = false } } } + private fun isCurrent(b: FragmentHomeBinding, country: String, generation: Long): Boolean = + _binding === b && isAdded && generation == feedGeneration && AppPrefs.countryOrDefault == country + override fun onDestroyView() { + feedGeneration++ + loadJob?.cancel() + loadedCountry = null + visibleCountry = null + lastLoadedAt = 0L super.onDestroyView() _binding = null } diff --git a/app/src/main/java/com/sparktube/app/ui/library/LibraryFragment.kt b/app/src/main/java/com/sparktube/app/ui/library/LibraryFragment.kt index 142ff9c..ef77c88 100644 --- a/app/src/main/java/com/sparktube/app/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/sparktube/app/ui/library/LibraryFragment.kt @@ -4,17 +4,22 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.MotionEvent +import android.os.Handler +import android.os.Looper import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.tabs.TabLayout import com.sparktube.app.R import com.sparktube.app.data.LocalStore import com.sparktube.app.databinding.FragmentLibraryBinding import com.sparktube.app.download.DownloadCenter import com.sparktube.app.playback.PlaybackCenter +import com.sparktube.app.playback.QueueEntry import com.sparktube.app.ui.channel.ChannelActivity import com.sparktube.app.ui.common.ChannelRowAdapter import com.sparktube.app.ui.common.DownloadAdapter @@ -24,6 +29,8 @@ import com.sparktube.app.ui.common.toEntry import com.sparktube.app.ui.common.toUiModel import com.sparktube.app.ui.player.PlayerActivity import com.sparktube.app.ui.playlist.PlaylistActivity +import com.sparktube.app.ui.music.AudioDownloadLibrary +import com.sparktube.app.ui.music.NowPlayingActivity /** * Library: History, Favorites, Subscriptions, Downloads and Playlists tabs. @@ -40,6 +47,12 @@ class LibraryFragment : Fragment() { private lateinit var playlistAdapter: PlaylistAdapter private var tab = TAB_HISTORY + private val uiHandler = Handler(Looper.getMainLooper()) + private val refreshDownloads = object : Runnable { + override fun run() { if (_binding != null && tab == TAB_DOWNLOADS) { refresh(); uiHandler.postDelayed(this, 1000) } } + } + private var downX = 0f + private var downY = 0f override fun onCreateView( inflater: LayoutInflater, @@ -78,8 +91,15 @@ class LibraryFragment : Fragment() { } ) downloadAdapter = DownloadAdapter( - onClick = { record -> PlaybackCenter.playDownload(record) }, - onLongClick = { record -> confirmDelete(record) } + onClick = { record -> openDownload(record) }, + onLongClick = { record -> confirmDelete(record) }, + onPause = { record -> + if (record.status != DownloadCenter.STATUS_PAUSED) AlertDialog.Builder(requireContext()) + .setMessage(R.string.download_process_limit).setPositiveButton(android.R.string.ok) { _, _ -> + DownloadCenter.togglePause(requireContext(), record); refresh() + }.setNegativeButton(android.R.string.cancel, null).show() + else { DownloadCenter.togglePause(requireContext(), record); refresh() } + } ) playlistAdapter = PlaylistAdapter( onClick = { playlist -> @@ -94,7 +114,9 @@ class LibraryFragment : Fragment() { binding.tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { override fun onTabSelected(tab: TabLayout.Tab) { this@LibraryFragment.tab = tab.position + uiHandler.removeCallbacks(refreshDownloads) refresh() + if (tab.position == TAB_DOWNLOADS) uiHandler.post(refreshDownloads) } override fun onTabUnselected(tab: TabLayout.Tab) = Unit @@ -102,16 +124,53 @@ class LibraryFragment : Fragment() { }) binding.tabs.selectTab(binding.tabs.getTabAt(tab)) + binding.list.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() { + private var switched = false + override fun onInterceptTouchEvent(rv: RecyclerView, event: MotionEvent): Boolean { + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { downX = event.x; downY = event.y; switched = false } + MotionEvent.ACTION_MOVE -> { + val dx = event.x - downX + val dy = event.y - downY + val threshold = 56 * resources.displayMetrics.density + if (!switched && kotlin.math.abs(dx) > threshold && kotlin.math.abs(dx) > kotlin.math.abs(dy) * 1.4f) { + switched = true + val direction = if (rv.layoutDirection == View.LAYOUT_DIRECTION_RTL) -1 else 1 + val next = (this@LibraryFragment.tab + (if (dx < 0) direction else -direction)).coerceIn(TAB_HISTORY, TAB_PLAYLISTS) + binding.tabs.getTabAt(next)?.select() + return true + } + } + } + return false + } + }) + } + + fun showDownloadsTab() { + if (_binding == null) return + binding.tabs.getTabAt(TAB_DOWNLOADS)?.select() + refresh() + uiHandler.removeCallbacks(refreshDownloads) + uiHandler.post(refreshDownloads) } override fun onResume() { super.onResume() refresh() + uiHandler.removeCallbacks(refreshDownloads) + if (tab == TAB_DOWNLOADS) uiHandler.post(refreshDownloads) } + override fun onPause() { uiHandler.removeCallbacks(refreshDownloads); super.onPause() } + override fun onHiddenChanged(hidden: Boolean) { super.onHiddenChanged(hidden) - if (!hidden) refresh() + uiHandler.removeCallbacks(refreshDownloads) + if (!hidden) { + refresh() + if (tab == TAB_DOWNLOADS) uiHandler.post(refreshDownloads) + } } private fun refresh() { @@ -144,14 +203,39 @@ class LibraryFragment : Fragment() { private fun bindDownloads() { val context = context ?: return DownloadCenter.refreshStatuses(context) - // Music downloads live on the Music page, not here. - val downloads = LocalStore.videoDownloads(context) - binding.list.adapter = downloadAdapter - downloadAdapter.submitList(downloads) + // Include audio too so every transfer can be paused, resumed and shared here. + val downloads = LocalStore.downloads(context) + if (binding.list.adapter !== downloadAdapter) binding.list.adapter = downloadAdapter + downloadAdapter.submitList(downloads) { downloadAdapter.refreshLiveMetrics() } binding.emptyView.setText(R.string.empty_downloads) binding.emptyView.isVisible = downloads.isEmpty() } + private fun openDownload(record: com.sparktube.app.data.DownloadRecord) { + if (record.type != DownloadCenter.TYPE_AUDIO) { + PlaybackCenter.playDownload(record) + return + } + val playable = AudioDownloadLibrary.playable(listOf(record)).firstOrNull() + if (playable == null) { + Toast.makeText(requireContext(), R.string.download_file_missing, Toast.LENGTH_SHORT).show() + return + } + val (audio, path) = playable + PlaybackCenter.playMusicQueue( + listOf(QueueEntry( + url = "file://$path", + title = audio.title, + uploader = audio.uploader, + thumbnailUrl = audio.thumbnailUrl, + durationSec = 0L, + isMusic = true + )), + 0 + ) + NowPlayingActivity.start(requireContext()) + } + private fun bindPlaylists() { val context = context ?: return val playlists = LocalStore.playlists(context) @@ -188,6 +272,7 @@ class LibraryFragment : Fragment() { } override fun onDestroyView() { + uiHandler.removeCallbacks(refreshDownloads) super.onDestroyView() _binding = null } diff --git a/app/src/main/java/com/sparktube/app/ui/music/AudioDownloadLibrary.kt b/app/src/main/java/com/sparktube/app/ui/music/AudioDownloadLibrary.kt new file mode 100644 index 0000000..dcbe56e --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/music/AudioDownloadLibrary.kt @@ -0,0 +1,20 @@ +package com.sparktube.app.ui.music + +import com.sparktube.app.data.DownloadRecord +import com.sparktube.app.download.DownloadCenter +import java.io.File + +/** All audio transfers stay visible; only completed, present files enter the music queue. */ +internal object AudioDownloadLibrary { + fun rows(downloads: List): List = + downloads.filter { it.type == DownloadCenter.TYPE_AUDIO } + + fun playable( + downloads: List, + fileExists: (String) -> Boolean = { File(it).isFile } + ): List> = rows(downloads).mapNotNull { record -> + if (record.status != DownloadCenter.STATUS_DONE) return@mapNotNull null + val path = record.filePaths.firstOrNull(fileExists) ?: return@mapNotNull null + record to path + } +} diff --git a/app/src/main/java/com/sparktube/app/ui/music/MusicListActivity.kt b/app/src/main/java/com/sparktube/app/ui/music/MusicListActivity.kt index 76cf9af..5c002b8 100644 --- a/app/src/main/java/com/sparktube/app/ui/music/MusicListActivity.kt +++ b/app/src/main/java/com/sparktube/app/ui/music/MusicListActivity.kt @@ -3,35 +3,41 @@ package com.sparktube.app.ui.music import android.content.Context import android.content.Intent import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import com.sparktube.app.R +import com.sparktube.app.data.DownloadRecord import com.sparktube.app.data.LocalStore import com.sparktube.app.data.VideoEntry import com.sparktube.app.databinding.ActivityMusicListBinding import com.sparktube.app.download.DownloadCenter import com.sparktube.app.playback.PlaybackCenter import com.sparktube.app.playback.QueueEntry +import com.sparktube.app.ui.common.DownloadAdapter import com.sparktube.app.ui.common.MusicRowAdapter import com.sparktube.app.ui.common.VideoUiModel import com.sparktube.app.util.Themes -import java.io.File -/** - * The music page's own lists: Favorites (songs favorited from the player) - * and Downloads (audio files). Playing anything here queues exactly this - * list — favorites play favorites one by one, downloads play downloads. - */ +/** Music favorites and the audio download library, including live transfers. */ class MusicListActivity : AppCompatActivity() { private lateinit var binding: ActivityMusicListBinding private lateinit var adapter: MusicRowAdapter - + private lateinit var downloadAdapter: DownloadAdapter private var favourites = true private var entries: List = emptyList() + private val uiHandler = Handler(Looper.getMainLooper()) + private val refreshDownloads = object : Runnable { + override fun run() { + reload() + uiHandler.postDelayed(this, 1000L) + } + } override fun onCreate(savedInstanceState: Bundle?) { Themes.apply(this) @@ -43,62 +49,96 @@ class MusicListActivity : AppCompatActivity() { binding.title.text = getString(if (favourites) R.string.music_favorites else R.string.music_downloads) - adapter = MusicRowAdapter( - onClick = { model -> - val index = entries.indexOfFirst { it.url == model.url } - if (index >= 0) { - PlaybackCenter.playMusicQueue(entries, index) - NowPlayingActivity.start(this) + adapter = MusicRowAdapter(onClick = { model -> + val index = entries.indexOfFirst { it.url == model.url } + if (index >= 0) playQueue(index) + }) + downloadAdapter = DownloadAdapter( + onClick = { record -> openDownload(record) }, + onLongClick = { record -> confirmDelete(record) }, + onPause = { record -> + if (record.status != DownloadCenter.STATUS_PAUSED) { + AlertDialog.Builder(this) + .setMessage(R.string.download_process_limit) + .setPositiveButton(android.R.string.ok) { _, _ -> + DownloadCenter.togglePause(this, record) + reload() + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } else { + DownloadCenter.togglePause(this, record) + reload() } }, - onLongClick = if (favourites) null else ({ model -> - val record = LocalStore.musicDownloads(this) - .firstOrNull { "file://" + it.filePaths.firstOrNull() == model.url } - if (record != null) { - confirmDelete(record) - } - }) + audioStyle = true ) binding.list.layoutManager = LinearLayoutManager(this) - binding.list.adapter = adapter - + binding.list.adapter = if (favourites) adapter else downloadAdapter + // Avoid a change animation on every one-second metrics refresh. + if (!favourites) binding.list.itemAnimator = null binding.backButton.setOnClickListener { finish() } binding.playAllButton.setOnClickListener { - if (entries.isNotEmpty()) { - PlaybackCenter.playMusicQueue(entries, 0) - NowPlayingActivity.start(this) - } + // Re-check disk and completion state at the instant Play all is tapped. + reload() + if (entries.isNotEmpty()) playQueue(0) } } override fun onResume() { super.onResume() - // A theme / accent change while we were in the background. Themes.recreateIfNeeded(this) + uiHandler.removeCallbacks(refreshDownloads) reload() + if (!favourites) uiHandler.postDelayed(refreshDownloads, 1000L) + } + + override fun onPause() { + uiHandler.removeCallbacks(refreshDownloads) + super.onPause() + } + + override fun onDestroy() { + uiHandler.removeCallbacks(refreshDownloads) + super.onDestroy() } private fun reload() { + val hasRows: Boolean if (favourites) { entries = LocalStore.musicFavorites(this).map { it.toQueueEntry() } + adapter.submitList(entries.map { it.toRowModel() }) binding.emptyView.setText(R.string.empty_music_favorites) + hasRows = entries.isNotEmpty() } else { DownloadCenter.refreshStatuses(this) - entries = LocalStore.musicDownloads(this) - .mapNotNull { record -> - val path = record.filePaths.firstOrNull { File(it).exists() } - ?: return@mapNotNull null - record.toQueueEntry(path) - } + val records = AudioDownloadLibrary.rows(LocalStore.downloads(this)) + entries = AudioDownloadLibrary.playable(records).map { (record, path) -> record.toQueueEntry(path) } + downloadAdapter.submitList(records) { downloadAdapter.refreshLiveMetrics() } binding.emptyView.setText(R.string.empty_music_downloads) + hasRows = records.isNotEmpty() } - adapter.submitList(entries.map { it.toRowModel() }) - binding.emptyView.isVisible = entries.isEmpty() - binding.summaryRow.isVisible = entries.isNotEmpty() + binding.emptyView.isVisible = !hasRows + binding.summaryRow.isVisible = hasRows binding.subtitle.text = getString(R.string.music_songs_fmt, entries.size) + binding.playAllButton.isEnabled = entries.isNotEmpty() + binding.playAllButton.alpha = if (entries.isNotEmpty()) 1f else 0.45f + } + + private fun playQueue(index: Int) { + PlaybackCenter.playMusicQueue(entries, index) + NowPlayingActivity.start(this) + } + + private fun openDownload(record: DownloadRecord) { + reload() + val path = AudioDownloadLibrary.playable(listOf(record)).firstOrNull()?.second + val index = entries.indexOfFirst { it.url == "file://$path" } + if (path != null && index >= 0) playQueue(index) + else Toast.makeText(this, R.string.download_file_missing, Toast.LENGTH_SHORT).show() } - private fun confirmDelete(record: com.sparktube.app.data.DownloadRecord) { + private fun confirmDelete(record: DownloadRecord) { AlertDialog.Builder(this) .setTitle(R.string.download_delete_confirm) .setMessage(record.title) @@ -124,29 +164,16 @@ class MusicListActivity : AppCompatActivity() { } private fun VideoEntry.toQueueEntry(): QueueEntry = QueueEntry( - url = url, - title = title, - uploader = uploader, - thumbnailUrl = thumbnailUrl, - durationSec = durationSec, - isMusic = true + url = url, title = title, uploader = uploader, thumbnailUrl = thumbnailUrl, + durationSec = durationSec, isMusic = true ) private fun QueueEntry.toRowModel(): VideoUiModel = VideoUiModel( - url = url, - title = title, - uploader = uploader, - thumbnailUrl = thumbnailUrl, - durationSec = durationSec, - viewCount = -1L, - uploadDate = "" + url = url, title = title, uploader = uploader, thumbnailUrl = thumbnailUrl, + durationSec = durationSec, viewCount = -1L, uploadDate = "" ) -private fun com.sparktube.app.data.DownloadRecord.toQueueEntry(path: String): QueueEntry = QueueEntry( - url = "file://$path", - title = title, - uploader = uploader, - thumbnailUrl = thumbnailUrl, - durationSec = 0L, - isMusic = true +private fun DownloadRecord.toQueueEntry(path: String): QueueEntry = QueueEntry( + url = "file://$path", title = title, uploader = uploader, thumbnailUrl = thumbnailUrl, + durationSec = 0L, isMusic = true ) diff --git a/app/src/main/java/com/sparktube/app/ui/music/NowPlayingActivity.kt b/app/src/main/java/com/sparktube/app/ui/music/NowPlayingActivity.kt index fbbef04..1adf7d6 100644 --- a/app/src/main/java/com/sparktube/app/ui/music/NowPlayingActivity.kt +++ b/app/src/main/java/com/sparktube/app/ui/music/NowPlayingActivity.kt @@ -14,6 +14,10 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.CancellationException +import com.sparktube.app.ui.player.PlayerActivity import coil.load import com.sparktube.app.R import com.sparktube.app.data.LocalStore @@ -55,11 +59,14 @@ class NowPlayingActivity : AppCompatActivity() { // Radio keeps adding songs while it plays: keep "Up next" current. override fun onQueueChanged() = bindUpNext() + override fun onSleepTimerChanged() = bindTimer() + override fun onSleepTimerExpired() { finish() } } private val progressRunnable = object : Runnable { override fun run() { updateProgress() + bindTimer() updateDownloadState() handler.postDelayed(this, 500) } @@ -83,6 +90,23 @@ class NowPlayingActivity : AppCompatActivity() { requestNotificationPermissionIfNeeded() binding.backButton.setOnClickListener { finish() } + binding.modeSwitch.setOnClickListener { + binding.modeSwitch.isEnabled = false + lifecycleScope.launch { + try { + val available = if (PlaybackCenter.mode == PlaybackCenter.Mode.VIDEO) { + PlaybackCenter.switchBackToVideo() + true + } else PlaybackCenter.switchMusicToVideo() + if (available) { + PlayerActivity.startResume(this@NowPlayingActivity) + finish() + } else Toast.makeText(this@NowPlayingActivity, R.string.switch_video_unavailable, Toast.LENGTH_SHORT).show() + } catch (e: CancellationException) { throw e } + catch (e: Exception) { Toast.makeText(this@NowPlayingActivity, e.message ?: getString(R.string.switch_video_unavailable), Toast.LENGTH_SHORT).show() } + finally { binding.modeSwitch.isEnabled = true } + } + } binding.playPauseButton.setOnClickListener { PlaybackCenter.togglePlayPause() } binding.prevButton.setOnClickListener { PlaybackCenter.previous() } binding.nextButton.setOnClickListener { PlaybackCenter.next() } @@ -97,6 +121,7 @@ class NowPlayingActivity : AppCompatActivity() { binding.radioButton.setOnClickListener { restartRadio() } binding.downloadButton.setOnClickListener { onDownloadClicked() } binding.queueButton.setOnClickListener { showQueue() } + binding.timerButton.setOnClickListener { SleepTimerSheet(this).show() } binding.upNextCard.setOnClickListener { showQueue() } // The artwork is always a square that fits the space the controls @@ -259,6 +284,11 @@ class NowPlayingActivity : AppCompatActivity() { } } + private fun bindTimer() { + binding.timerButton.text = SleepTimerSheet.buttonLabel(this) + binding.timerButton.contentDescription = SleepTimerSheet.accessibilityLabel(this) + } + private fun showQueue() { QueueSheet(this).show() } diff --git a/app/src/main/java/com/sparktube/app/ui/music/SleepTimerSheet.kt b/app/src/main/java/com/sparktube/app/ui/music/SleepTimerSheet.kt new file mode 100644 index 0000000..c55f282 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/music/SleepTimerSheet.kt @@ -0,0 +1,125 @@ +package com.sparktube.app.ui.music + +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.drawable.GradientDrawable +import android.view.Gravity +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.TextView +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.sparktube.app.R +import com.sparktube.app.playback.PlaybackCenter +import com.sparktube.app.playback.SleepTimerState +import java.util.Locale + +/** Same selector for the music screen and the video action row. */ +class SleepTimerSheet(context: Context) : BottomSheetDialog(context) { + private fun dp(n: Int) = (n * context.resources.displayMetrics.density).toInt() + private val green = context.getColor(R.color.music_green) + private val textColor = context.getColor(R.color.on_surface) + private val root = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + setPadding(0, dp(10), 0, dp(16)) + background = GradientDrawable().apply { + setColor(context.getColor(R.color.sheet_bg)) + cornerRadii = floatArrayOf(dp(24).toFloat(), dp(24).toFloat(), dp(24).toFloat(), dp(24).toFloat(), 0f, 0f, 0f, 0f) + setStroke(dp(1), green) + } + } + private val listener = object : PlaybackCenter.Listener { + override fun onSleepTimerExpired() { dismiss() } + override fun onItemChanged(entry: com.sparktube.app.playback.QueueEntry?) { dismiss() } + } + + init { + root.addView(android.view.View(context).apply { + background = context.getDrawable(R.drawable.bg_sheet_handle) + }, LinearLayout.LayoutParams(dp(36), dp(4)).apply { gravity = Gravity.CENTER_HORIZONTAL; bottomMargin = dp(12) }) + root.addView(TextView(context).apply { + text = context.getString(R.string.sleep_timer_title) + textSize = 22f + setTextColor(textColor) + setTypeface(typeface, Typeface.BOLD) + setPadding(dp(22), dp(8), dp(22), dp(16)) + }) + root.addView(android.view.View(context).apply { setBackgroundColor(context.getColor(R.color.outline)) }, LinearLayout.LayoutParams(-1, dp(1))) + val rows = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL } + val mode = PlaybackCenter.sleepTimerMode + addRow(rows, context.getString(R.string.sleep_timer_off), R.drawable.ic_close, mode == SleepTimerState.Mode.OFF) { + PlaybackCenter.clearSleepTimer() + } + for (minutes in listOf(10, 15, 20, 30, 45, 60)) { + val label = if (minutes == 60) context.getString(R.string.sleep_timer_hour) + else context.getString(R.string.sleep_timer_minutes, minutes) + addRow(rows, label, R.drawable.ic_sleep_timer, + mode == SleepTimerState.Mode.DURATION && PlaybackCenter.sleepTimerMinutes == minutes) { + PlaybackCenter.setSleepTimer(minutes) + } + } + val remaining = (PlaybackCenter.durationMs - PlaybackCenter.positionMs).coerceAtLeast(0) + val end = context.getString(R.string.sleep_timer_end) + addRow(rows, if (PlaybackCenter.durationMs > 0) "$end (${formatRemaining(remaining)})" else end, + R.drawable.ic_sleep_timer, mode == SleepTimerState.Mode.END_OF_ITEM) { + PlaybackCenter.setSleepTimerEndOfItem() + } + val scroll = ScrollView(context).apply { isFillViewport = false; addView(rows) } + root.addView(scroll, LinearLayout.LayoutParams(-1, -2)) + setContentView(root) + behavior.skipCollapsed = true + setOnShowListener { + (root.parent as? android.view.View)?.setBackgroundColor(Color.TRANSPARENT) + root.layoutParams = root.layoutParams.apply { + height = minOf(root.measuredHeight, (context.resources.displayMetrics.heightPixels * 0.86f).toInt()) + } + behavior.state = BottomSheetBehavior.STATE_EXPANDED + } + } + + private fun addRow(parent: LinearLayout, label: String, icon: Int, selected: Boolean, action: () -> Unit) { + val tint = if (selected) green else textColor + val row = LinearLayout(context).apply { + gravity = Gravity.CENTER_VERTICAL + orientation = LinearLayout.HORIZONTAL + minimumHeight = dp(48) + setPadding(dp(22), dp(14), dp(22), dp(14)) + isClickable = true + isFocusable = true + isSelected = selected + contentDescription = if (selected) context.getString(R.string.sleep_timer_selected, label) else label + val out = android.util.TypedValue() + context.theme.resolveAttribute(android.R.attr.selectableItemBackground, out, true) + setBackgroundResource(out.resourceId) + setOnClickListener { action(); dismiss() } + } + row.addView(ImageView(context).apply { setImageResource(icon); setColorFilter(tint); importantForAccessibility = android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO }, LinearLayout.LayoutParams(dp(24), dp(24))) + row.addView(TextView(context).apply { text = label; textSize = 18f; setTextColor(tint); setPadding(dp(20), 0, dp(8), 0) }, LinearLayout.LayoutParams(0, -2, 1f)) + row.addView(ImageView(context).apply { + setImageResource(R.drawable.ic_check); setColorFilter(green) + visibility = if (selected) android.view.View.VISIBLE else android.view.View.INVISIBLE + importantForAccessibility = android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO + }, LinearLayout.LayoutParams(dp(24), dp(24))) + parent.addView(row, LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)) + } + + override fun onStart() { super.onStart(); PlaybackCenter.addListener(listener) } + override fun onStop() { PlaybackCenter.removeListener(listener); super.onStop() } + + companion object { + private fun formatRemaining(ms: Long): String { + val seconds = (ms.coerceAtLeast(0) + 999) / 1000 + return String.format(Locale.getDefault(), "%d:%02d", seconds / 60, seconds % 60) + } + fun buttonLabel(context: Context): String = when (PlaybackCenter.sleepTimerMode) { + SleepTimerState.Mode.OFF -> context.getString(R.string.sleep_timer_button) + SleepTimerState.Mode.DURATION -> formatRemaining(PlaybackCenter.sleepTimerRemainingMs ?: 0) + SleepTimerState.Mode.END_OF_ITEM -> context.getString(R.string.sleep_timer_end_countdown, + formatRemaining((PlaybackCenter.durationMs - PlaybackCenter.positionMs).coerceAtLeast(0))) + } + fun accessibilityLabel(context: Context): String = context.getString(R.string.sleep_timer_accessibility, buttonLabel(context)) + } +} diff --git a/app/src/main/java/com/sparktube/app/ui/player/DeliberateSeekTimeBar.kt b/app/src/main/java/com/sparktube/app/ui/player/DeliberateSeekTimeBar.kt new file mode 100644 index 0000000..8228091 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/player/DeliberateSeekTimeBar.kt @@ -0,0 +1,181 @@ +package com.sparktube.app.ui.player + +import android.content.Context +import android.content.res.Configuration +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.ViewConfiguration +import androidx.media3.common.util.UnstableApi +import androidx.media3.ui.DefaultTimeBar + +/** Native Media3 time bar, with a portrait-only deliberate-drag touch gate. + * Keyboard and accessibility seeking retain Media3's implementation. + */ +@UnstableApi +class DeliberateSeekTimeBar @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : DefaultTimeBar(context, attrs, defStyleAttr, attrs, + androidx.media3.ui.R.style.ExoStyledControls_TimeBar) { + private val gate = SeekBarDragGate() + private var pendingDown: MotionEvent? = null + private var nativeDragging = false + private var portraitSequence = false + private val dragSlop = maxOf(ViewConfiguration.get(context).scaledTouchSlop.toFloat(), + 14f * resources.displayMetrics.density) + + // Read the same style as DefaultTimeBar. Its 48dp seekBounds are intentionally + // NOT used: only the painted 2dp track or the current circular thumb qualify. + private val touchStyle = context.obtainStyledAttributes(attrs, + androidx.media3.ui.R.styleable.DefaultTimeBar, defStyleAttr, + androidx.media3.ui.R.style.ExoStyledControls_TimeBar) + private val trackHeight = touchStyle.getDimensionPixelSize( + androidx.media3.ui.R.styleable.DefaultTimeBar_bar_height, dp(4)) + private val bottomGravity = touchStyle.getInt( + androidx.media3.ui.R.styleable.DefaultTimeBar_bar_gravity, 0) == 1 + private val enabledThumbSize = touchStyle.getDimensionPixelSize( + androidx.media3.ui.R.styleable.DefaultTimeBar_scrubber_enabled_size, dp(12)) + private val draggedThumbSize = touchStyle.getDimensionPixelSize( + androidx.media3.ui.R.styleable.DefaultTimeBar_scrubber_dragged_size, dp(16)) + private val disabledThumbSize = touchStyle.getDimensionPixelSize( + androidx.media3.ui.R.styleable.DefaultTimeBar_scrubber_disabled_size, 0) + private val thumbInset = (maxOf(enabledThumbSize, draggedThumbSize, disabledThumbSize) + 1) / 2 + private var insetDisabled = false + private var thumbReadyAt = 0L + private var touchPositionMs = 0L + private var touchDurationMs = 0L + private var paintedTrack = SeekBarTouchGeometry(0, 0, 0, 0) + + init { touchStyle.recycle() } + + private fun dp(value: Int) = (value * resources.displayMetrics.density + 0.5f).toInt() + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + paintedTrack = SeekBarTouchGeometry.layout(width, height, paddingLeft, paddingRight, + paddingBottom, trackHeight, if (insetDisabled) 0 else thumbInset, bottomGravity) + } + + override fun setPosition(position: Long) { + touchPositionMs = position + super.setPosition(position) + } + + override fun setDuration(duration: Long) { + touchDurationMs = duration + super.setDuration(duration) + } + + override fun showScrubber() { + insetDisabled = false + thumbReadyAt = 0L + super.showScrubber() + } + + override fun showScrubber(animationDuration: Long) { + insetDisabled = false + // During scaling accept only the track, never a larger-than-painted thumb. + thumbReadyAt = android.os.SystemClock.uptimeMillis() + animationDuration + super.showScrubber(animationDuration) + } + + override fun hideScrubber(disableScrubberPadding: Boolean) { + insetDisabled = disableScrubberPadding + thumbReadyAt = Long.MAX_VALUE + super.hideScrubber(disableScrubberPadding) + } + + override fun hideScrubber(animationDuration: Long) { + thumbReadyAt = Long.MAX_VALUE + super.hideScrubber(animationDuration) + } + + /** Used by Activity routing too, so transparent padding remains video surface. */ + fun hitsVisibleSeekBar(x: Float, y: Float): Boolean { + if (!isShown || !isEnabled || alpha <= 0f) return false + val radius = if (android.os.SystemClock.uptimeMillis() >= thumbReadyAt) + (if (isFocused) draggedThumbSize else enabledThumbSize) / 2 else 0 + return paintedTrack.contains(x, y, touchPositionMs, touchDurationMs, radius) + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + cancelPortraitSequence() + portraitSequence = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE + } + // Exact native touch behavior for sequences started in landscape. + if (!portraitSequence) return super.onTouchEvent(event) + if (!isEnabled || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + cancelPortraitSequence() + return true + } + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + gate.down(event.x, event.y, event.pointerCount == 1 && hitsVisibleSeekBar(event.x, event.y)) + if (gate.state == SeekBarDragGate.State.REJECTED) return false + pendingDown = MotionEvent.obtain(event) + } + MotionEvent.ACTION_MOVE -> { + if (event.pointerCount != 1) { + cancelPortraitSequence() + gate.reject() + return true + } + if (gate.move(event.x, event.y, dragSlop)) { + if (!nativeDragging) { + val down = pendingDown ?: return true + nativeDragging = super.onTouchEvent(down) + down.recycle() + pendingDown = null + if (!nativeDragging) gate.reject() + } + if (nativeDragging) super.onTouchEvent(event) + } + } + MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_POINTER_UP -> { + cancelPortraitSequence() + gate.reject() + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + // No DOWN/UP is delivered for taps or rejected drags, so no + // transient thumb jump, scrub listener, or playback seek occurs. + if (nativeDragging) super.onTouchEvent(event) + nativeDragging = false + clearPending() + gate.reset() + portraitSequence = false + } + } + return true + } + + private fun clearPending() { + pendingDown?.recycle() + pendingDown = null + } + + private fun cancelPortraitSequence() { + if (nativeDragging) { + val now = android.os.SystemClock.uptimeMillis() + val cancel = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0f, 0f, 0) + super.onTouchEvent(cancel) + cancel.recycle() + } + nativeDragging = false + clearPending() + gate.reset() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + cancelPortraitSequence() + // Keep an interrupted portrait sequence consumed until its UP/CANCEL; + // never forward an orphan MOVE to the native landscape bar. + super.onConfigurationChanged(newConfig) + } + + override fun onDetachedFromWindow() { + cancelPortraitSequence() + super.onDetachedFromWindow() + } +} diff --git a/app/src/main/java/com/sparktube/app/ui/player/PlayerActivity.kt b/app/src/main/java/com/sparktube/app/ui/player/PlayerActivity.kt index ebfb919..abae69b 100644 --- a/app/src/main/java/com/sparktube/app/ui/player/PlayerActivity.kt +++ b/app/src/main/java/com/sparktube/app/ui/player/PlayerActivity.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo import android.content.res.Configuration +import android.media.AudioManager import android.graphics.Typeface import android.graphics.drawable.GradientDrawable import android.os.Build @@ -31,6 +32,7 @@ import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.doOnPreDraw import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams +import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView @@ -83,6 +85,16 @@ class PlayerActivity : AppCompatActivity() { private var swipeTriggered = false private lateinit var gestureDetector: GestureDetector + private var gestureTracking = false + private var gestureStartedInLandscape = false + private var portraitGestureRejected = false + private var gestureStartVolume = 0 + private var gestureStartBrightness = 0.5f + private var gestureAxis = PlayerGestureMath.Axis.NONE + private var gestureStartPosition = 0L + private var pendingSeekTarget: Long? = null + private val hideGestureFeedback = Runnable { binding.gestureFeedback.isVisible = false } + private var retiringPip = false /** Mirrors the built-in controller's visibility (gear / seek controls). */ private var controllerVisible = false @@ -98,21 +110,39 @@ class PlayerActivity : AppCompatActivity() { private val playbackListener = object : PlaybackCenter.Listener { override fun onItemChanged(entry: QueueEntry?) { + updateQueueControls() if (entry != null && entry.url != loadedUrl) { loadedUrl = entry.url bind(entry) } } + override fun onQueueChanged() = updateQueueControls() + override fun onSleepTimerExpired() { finish() } + override fun onSleepTimerChanged() { + binding.actionTimerLabel.text = com.sparktube.app.ui.music.SleepTimerSheet.buttonLabel(this@PlayerActivity) + binding.actionTimer.contentDescription = com.sparktube.app.ui.music.SleepTimerSheet.accessibilityLabel(this@PlayerActivity) + } + + override fun onPlaybackStateChanged(isPlaying: Boolean) = updateQueueControls() + + override fun onVideoFrameRendered() { + binding.errorView.isVisible = false + binding.loading.isVisible = false + } + override fun onResolvingChanged(isResolving: Boolean) { + updateQueueControls() binding.loading.isVisible = isResolving && !binding.errorView.isVisible } override fun onCatalogReady() { + updateQueueControls() bindCatalog() } override fun onError(message: String) { + binding.loading.isVisible = false binding.errorText.text = message binding.errorView.isVisible = true } @@ -138,6 +168,8 @@ class PlayerActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { Themes.apply(this) super.onCreate(savedInstanceState) + // A fresh watch page must not leave the previous pinned Activity behind. + PlaybackCenter.closePreviousPip() // Expands UP out of the mini player (and collapses back down). PanelTransitions.install(this) @@ -176,6 +208,7 @@ class PlayerActivity : AppCompatActivity() { object : PlayerView.ControllerVisibilityListener { override fun onVisibilityChanged(visibility: Int) { controllerVisible = visibility == View.VISIBLE + updateQueueControls() syncFullscreenButtonVisibility() } } @@ -204,17 +237,34 @@ class PlayerActivity : AppCompatActivity() { binding.relatedList.adapter = relatedAdapter binding.retryButton.setOnClickListener { retry() } + // Same collapse action as NowPlayingActivity: keep the shared queue/player alive. + binding.backButton.setOnClickListener { finish() } binding.actionFavorite.setOnClickListener { toggleFavorite() } binding.actionPlaylist.setOnClickListener { showPlaylistSheet() } binding.actionShare.setOnClickListener { shareVideo() } binding.actionDownload.setOnClickListener { onDownloadClicked() } - binding.actionBackground.setOnClickListener { - PlaybackCenter.switchToAudioOnly() - Toast.makeText(this, R.string.background_started, Toast.LENGTH_SHORT).show() - finish() + binding.actionTimerLabel.text = com.sparktube.app.ui.music.SleepTimerSheet.buttonLabel(this) + binding.actionTimer.setOnClickListener { + com.sparktube.app.ui.music.SleepTimerSheet(this).show() } binding.actionPip.setOnClickListener { enterPipOrToast() } + binding.modeSwitch.setOnClickListener { openAudioView() } + binding.playerView.setShowRewindButton(false) + binding.playerView.setShowFastForwardButton(false) + binding.queuePrevious.setOnClickListener { + PlaybackCenter.previous() + binding.playerView.showController() + } + binding.queueNext.setOnClickListener { + PlaybackCenter.next() + binding.playerView.showController() + } + binding.queuePlayPause.setOnClickListener { + PlaybackCenter.togglePlayPause() + binding.playerView.showController() + updateQueueControls() + } binding.channelInfo.setOnClickListener { openChannel() } binding.subscribeButton.setOnClickListener { toggleSubscribe() } @@ -229,37 +279,85 @@ class PlayerActivity : AppCompatActivity() { gestureDetector = GestureDetector(this, object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { swipeTriggered = false - return false + gestureAxis = PlayerGestureMath.Axis.NONE + portraitGestureRejected = false + gestureStartPosition = PlaybackCenter.positionMs + pendingSeekTarget = null + val audio = getSystemService(Context.AUDIO_SERVICE) as AudioManager + gestureStartVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC) + gestureStartBrightness = window.attributes.screenBrightness.takeIf { it >= 0f } + ?: (android.provider.Settings.System.getInt(contentResolver, + android.provider.Settings.System.SCREEN_BRIGHTNESS, 128) / 255f) + return true } - override fun onScroll( - e1: MotionEvent?, - e2: MotionEvent, - dx: Float, - dy: Float - ): Boolean { - // Total drag distance from the initial touch (e1) to the - // current position: the per-event dy the API reports is only - // a few pixels per move, so a threshold on it almost never - // fired — the swipe-down-to-minimize felt dead. + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (binding.playerView.isControllerFullyVisible) binding.playerView.hideController() + else binding.playerView.showController() + return true + } + + override fun onDoubleTap(e: MotionEvent): Boolean { + // Preserve intentional double-tap seeks in both orientations. + if (!canSeekFromVideoSurface()) return true + val forward = e.x >= binding.playerView.width / 2f + PlaybackCenter.seekTo(PlayerGestureMath.seekTarget(PlaybackCenter.positionMs, PlaybackCenter.durationMs, forward)) + showGestureFeedback(getString(if (forward) R.string.gesture_seek_forward else R.string.gesture_seek_back)) + return true + } + + override fun onScroll(e1: MotionEvent?, e2: MotionEvent, dx: Float, dy: Float): Boolean { val start = e1 ?: return false - val totalDy = e2.y - start.y - val totalDx = kotlin.math.abs(e2.x - start.x) - if (!swipeTriggered && totalDy > SWIPE_MIN_DISTANCE && totalDy > totalDx * 1.4f) { - swipeTriggered = true - // Swipe down on the video collapses playback into the - // mini player, like the official YouTube app: the playback - // service keeps playing and MainActivity's mini player - // picks the video surface up. - finish() + val totalY = start.y - e2.y + val totalX = e2.x - start.x + if (portraitGestureRejected) return true + if (gestureAxis == PlayerGestureMath.Axis.NONE) { + gestureAxis = PlayerGestureMath.chooseAxis(totalX, totalY, dp(14).toFloat()) + if (PlayerGestureMath.rejectAmbiguousPortraitMovement( + gestureStartedInLandscape, totalX, totalY, dp(14).toFloat())) { + portraitGestureRejected = true + return true + } + if (gestureAxis != PlayerGestureMath.Axis.NONE) { + swipeTriggered = true + } } - return false + if (gestureAxis == PlayerGestureMath.Axis.NONE) return true + if (gestureAxis == PlayerGestureMath.Axis.HORIZONTAL) { + // Still consume this gesture: dragging into the bar must not + // transfer ownership or synthesize a new seek-bar ACTION_DOWN. + if (!canSeekFromVideoSurface()) return true + val pixelsPerTenSeconds = dp(120).toFloat().coerceAtLeast(1f) + pendingSeekTarget = PlayerGestureMath.horizontalSeekTarget( + gestureStartPosition, PlaybackCenter.durationMs, totalX, pixelsPerTenSeconds + ) + showGestureFeedback(formatGestureSeek(pendingSeekTarget!!)) + return true + } + val width = binding.playerView.width + // The center retains swipe-down-to-minimize; sides adjust media levels. + if (start.x > width * 0.35f && start.x < width * 0.65f) { + if (totalY < -dp(100)) finish() + return true + } + // Side brightness and volume adjustments are landscape only. + if (resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE) return true + val fraction = totalY / (binding.playerView.height * 0.75f).coerceAtLeast(1f) + if (start.x >= width / 2f) { + val audio = getSystemService(Context.AUDIO_SERVICE) as AudioManager + val max = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + val volume = PlayerGestureMath.volume(gestureStartVolume, fraction, max) + try { audio.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0) } + catch (_: SecurityException) { return true } + showGestureFeedback(getString(R.string.gesture_volume, if (max > 0) volume * 100 / max else 0)) + } else { + val brightness = PlayerGestureMath.brightness(gestureStartBrightness, fraction) + window.attributes = window.attributes.apply { screenBrightness = brightness } + showGestureFeedback(getString(R.string.gesture_brightness, (brightness * 100).toInt())) + } + return true } }) - binding.playerView.setOnTouchListener { v, event -> - gestureDetector.onTouchEvent(event) - v.onTouchEvent(event) - } hookGearMenu() @@ -286,6 +384,103 @@ class PlayerActivity : AppCompatActivity() { } } + /** Keep unavailable queue actions visible but dimmed, never replace them with seek. */ + private fun updateQueueControls() { + if (!::binding.isInitialized) return + val player = PlaybackCenter.sessionPlayer + listOf( + binding.queuePrevious to Player.COMMAND_SEEK_TO_PREVIOUS, + binding.queueNext to Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM + ).forEach { (button, command) -> + button.isEnabled = player.isCommandAvailable(command) + button.alpha = if (button.isEnabled) 1f else 0.35f + } + val playing = player.playWhenReady && player.playbackState != Player.STATE_ENDED + binding.queuePlayPause.setImageResource(if (playing) R.drawable.ic_pause else R.drawable.ic_play_arrow) + binding.queuePlayPause.contentDescription = getString( + if (playing) Media3R.string.exo_controls_pause_description else Media3R.string.exo_controls_play_description) + binding.transportControls.isVisible = controllerVisible && !isInPictureInPictureMode + } + + private fun openAudioView() { + PlaybackCenter.switchToAudioOnly() + com.sparktube.app.ui.music.NowPlayingActivity.start(this) + finish() + } + + private fun showGestureFeedback(text: String) { + binding.gestureFeedback.text = text + binding.gestureFeedback.isVisible = true + downloadHandler.removeCallbacks(hideGestureFeedback) + downloadHandler.postDelayed(hideGestureFeedback, 900) + } + + private fun formatGestureSeek(positionMs: Long): String { + val seconds = positionMs / 1_000L + return "${seconds / 60}:${(seconds % 60).toString().padStart(2, '0')}" + } + + private fun canSeekFromVideoSurface(): Boolean = PlayerGestureMath.canSeekVideoSurface( + gestureStartedInLandscape, + resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + ) + + // Dispatch above the controller's empty scrim, but leave real buttons and the seek bar alone. + override fun dispatchTouchEvent(event: MotionEvent): Boolean { + if (!::gestureDetector.isInitialized || isInPictureInPictureMode) return super.dispatchTouchEvent(event) + val point = IntArray(2) + binding.playerView.getLocationOnScreen(point) + val x = event.rawX - point[0] + val y = event.rawY - point[1] + if (event.actionMasked == MotionEvent.ACTION_DOWN) { + portraitGestureRejected = false + gestureStartedInLandscape = resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE + val inside = x >= 0 && x < binding.playerView.width && y >= 0 && y < binding.playerView.height + gestureTracking = inside && !hitsPlayerControl(binding.root, event.rawX.toInt(), event.rawY.toInt()) + } + if (gestureTracking) { + val local = MotionEvent.obtain(event) + local.setLocation(x, y) + if (!gestureStartedInLandscape && (event.pointerCount > 1 || + event.actionMasked == MotionEvent.ACTION_POINTER_DOWN)) { + local.action = MotionEvent.ACTION_CANCEL + gestureDetector.onTouchEvent(local) + portraitGestureRejected = true + pendingSeekTarget = null + } else if (!portraitGestureRejected) { + gestureDetector.onTouchEvent(local) + } + local.recycle() + if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) { + if (event.actionMasked == MotionEvent.ACTION_UP && !portraitGestureRejected && canSeekFromVideoSurface()) { + pendingSeekTarget?.let(PlaybackCenter::seekTo) + } + pendingSeekTarget = null + gestureTracking = false + } + return true + } + return super.dispatchTouchEvent(event) + } + + private fun hitsPlayerControl(view: View, x: Int, y: Int): Boolean { + if (!view.isShown) return false + val rect = android.graphics.Rect() + if (!view.getGlobalVisibleRect(rect) || !rect.contains(x, y)) return false + if (view is DeliberateSeekTimeBar && + resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE) { + val location = IntArray(2) + view.getLocationOnScreen(location) + return view.hitsVisibleSeekBar((x - location[0]).toFloat(), (y - location[1]).toFloat()) + } + if (view is android.widget.ImageButton || view is android.widget.Button || + view is androidx.media3.ui.DefaultTimeBar || view === binding.modeSwitch) return true + if (view is ViewGroup) { + for (i in 0 until view.childCount) if (hitsPlayerControl(view.getChildAt(i), x, y)) return true + } + return false + } + /** * Puts quality / speed / audio-track selection behind the gear button of * the player's built-in controller. @@ -295,10 +490,14 @@ class PlayerActivity : AppCompatActivity() { gear?.setOnClickListener { showVideoSettingsSheet() } } - /** Fullscreen + scale buttons follow the built-in controller's visibility. */ + /** All overlay buttons follow the built-in controller's visibility. */ private fun syncFullscreenButtonVisibility() { val visible = !isInPictureInPictureMode && controllerVisible + // Single-tap reveal and auto-hide apply to collapse just like transport. + binding.backButton.isVisible = visible + binding.transportControls.isVisible = visible binding.fullscreenButton.isVisible = visible + binding.modeSwitch.isVisible = visible // Fit / Crop / Stretch is a fullscreen-only control: the portrait // box always shows the full frame, so the button stays hidden there. binding.scaleButton.isVisible = visible && @@ -339,12 +538,17 @@ class PlayerActivity : AppCompatActivity() { val endInset = if (rtl) bars.left else bars.right binding.backButton.updateLayoutParams { - topMargin = bars.top + dp(4) + // Portrait playerView already clears the status bar; don't add it twice. + topMargin = dp(4) + if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) bars.top else 0 marginStart = dp(4) + startInset } binding.fullscreenButton.updateLayoutParams { - topMargin = bars.top + dp(4) marginEnd = endInset + bottomMargin = dp(48) + } + binding.modeSwitch.updateLayoutParams { + topMargin = dp(4) + marginEnd = endInset + dp(4) } binding.scaleButton.updateLayoutParams { topMargin = bars.top + dp(4) @@ -421,24 +625,25 @@ class PlayerActivity : AppCompatActivity() { override fun onStart() { super.onStart() + if (retiringPip || isFinishing) return PlaybackCenter.addListener(playbackListener) + // A visible PiP activity may be STARTED/PAUSED, never RESUMED. + PlaybackCenter.setPipState(binding.playerView, isInPictureInPictureMode, ::closeReplacedPip) + PlaybackCenter.attachView(binding.playerView) + updateQueueControls() // Playback can start resolving BEFORE this page is listening (Play // all / a playlist video starts the queue, then opens the page), so // pick the spinner state up here instead of missing it. binding.loading.isVisible = PlaybackCenter.isResolving && !binding.errorView.isVisible downloadHandler.post(downloadPoll) - if (PlaybackCenter.hasMedia) { - PlaybackCenter.player.playWhenReady = true - } } override fun onResume() { super.onResume() + if (retiringPip || isFinishing) return if (Themes.recreateIfNeeded(this)) return applyCloseTransition() - if (!isInPictureInPictureMode) { - PlaybackCenter.attachView(binding.playerView) - } + PlaybackCenter.attachView(binding.playerView) } // ----- Close animation ----- @@ -472,16 +677,20 @@ class PlayerActivity : AppCompatActivity() { } } - override fun onPause() { - super.onPause() - if (!isInPictureInPictureMode) { - PlaybackCenter.detachView(binding.playerView) - } - } + // Do NOT detach in onPause: Android/ROMs can pause us before delivering + // the PiP-mode callback. PiP stays visible while paused and needs the + // same TextureView output. Detach only once the activity is not visible. override fun onStop() { super.onStop() + PlaybackCenter.detachView(binding.playerView) + // A stopped/temporarily hidden pinned Activity still needs to be + // reachable for replacement cleanup; only a real exit releases it. + if (!isInPictureInPictureMode || isFinishing || retiringPip) { + PlaybackCenter.setPipState(binding.playerView, false) + } downloadHandler.removeCallbacks(downloadPoll) + downloadHandler.removeCallbacks(hideGestureFeedback) PlaybackCenter.removeListener(playbackListener) // Return to sensor portrait so the next video starts the standard way. if (requestedOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { @@ -489,10 +698,27 @@ class PlayerActivity : AppCompatActivity() { } } + override fun onDestroy() { + PlaybackCenter.setPipState(binding.playerView, false) + PlaybackCenter.detachView(binding.playerView) + super.onDestroy() + } + // ----- PiP ----- + private fun closeReplacedPip() { + if (retiringPip) return + retiringPip = true + PlaybackCenter.removeListener(playbackListener) + PlaybackCenter.detachView(binding.playerView) + // Finish the pinned Activity itself, not just its surface or global flag. + // Never stop/pause the shared engine: it is about to play the new item. + finish() + } + override fun onUserLeaveHint() { super.onUserLeaveHint() + if (retiringPip || isFinishing) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && PlaybackCenter.hasMedia && PlaybackCenter.mode == PlaybackCenter.Mode.VIDEO && !PlaybackCenter.audioOnlyMode ) { @@ -509,7 +735,7 @@ class PlayerActivity : AppCompatActivity() { } private fun enterPip() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || retiringPip || isFinishing) return try { enterPictureInPictureMode( android.app.PictureInPictureParams.Builder() @@ -526,30 +752,34 @@ class PlayerActivity : AppCompatActivity() { newConfig: Configuration ) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) - PlaybackCenter.inPip = isInPictureInPictureMode - if (isInPictureInPictureMode) { - // Show the video only: no back button, no fullscreen button, no page. - binding.backButton.isVisible = false - binding.fullscreenButton.isVisible = false - binding.scaleButton.isVisible = false - binding.scrollArea.isVisible = false - binding.loading.isVisible = false - binding.errorView.isVisible = false - // Expand player to the whole window - binding.playerView.updateLayoutParams { - height = ViewGroup.LayoutParams.MATCH_PARENT - dimensionRatio = null - } - // The PiP window has no insets — clear the portrait offset. - applyPlayerTopInset() - } else { - binding.scrollArea.isVisible = true - binding.playerView.updateLayoutParams { - height = 0 - dimensionRatio = "16:9" - } - applyPlayerTopInset() - syncFullscreenButtonVisibility() + if (retiringPip || isFinishing) return + PlaybackCenter.setPipState(binding.playerView, isInPictureInPictureMode, ::closeReplacedPip) + // Recover ownership even if pause/configuration callbacks arrived + // first. attachView is idempotent and uses the raw rendering player. + if (isInPictureInPictureMode) PlaybackCenter.attachView(binding.playerView) + binding.playerView.useController = !isInPictureInPictureMode + applyOrientation(newConfig) + if (!isInPictureInPictureMode) binding.playerView.showController() + } + + private fun applyPipLayout() { + // Both mode and configuration callbacks use this path: orientation + // must never restore the watch page over the tiny PiP video output. + binding.transportControls.isVisible = false + binding.playerView.hideController() + binding.backButton.isVisible = false + binding.fullscreenButton.isVisible = false + binding.scaleButton.isVisible = false + binding.modeSwitch.isVisible = false + binding.gestureFeedback.isVisible = false + binding.scrollArea.isVisible = false + binding.loading.isVisible = false + binding.errorView.isVisible = false + binding.playerView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT + binding.playerView.updateLayoutParams { + height = ViewGroup.LayoutParams.MATCH_PARENT + dimensionRatio = null + topMargin = 0 } } @@ -561,7 +791,36 @@ class PlayerActivity : AppCompatActivity() { } private fun applyOrientation(config: Configuration) { + if (isInPictureInPictureMode) { + applyPipLayout() + return + } val landscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE + if (!landscape) { + // Do not carry a fullscreen-only brightness override back to portrait. + window.attributes = window.attributes.apply { + screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE + } + binding.gestureFeedback.isVisible = false + } + // This activity handles rotation without reinflating the controller. + // Clear only the portrait bottom strip; restore Media3's original + // background in landscape without changing seek layout or behavior. + binding.playerView.findViewById(Media3R.id.exo_bottom_bar)?.setBackgroundResource( + if (landscape) Media3R.color.exo_bottom_bar_background else android.R.color.transparent + ) + // The portrait-only dimension is also read by Media3's layout manager, + // so resizing cannot put the old 52dp gap back. Apply it immediately + // on rotation too, before the next controller size/layout callback. + binding.playerView.findViewById(Media3R.id.exo_progress) + ?.updateLayoutParams { + bottomMargin = resources.getDimensionPixelSize(Media3R.dimen.exo_styled_progress_margin_bottom) + } + // Keep settings clickable above the lowered 48dp seek touch target. + // Empty transparent row space still passes touches through to the bar. + // Restore the original zero Z/order for landscape controls. + binding.playerView.findViewById(Media3R.id.exo_bottom_bar)?.translationZ = + if (landscape) 0f else dp(1).toFloat() binding.fullscreenButton.setImageResource( if (landscape) R.drawable.ic_fullscreen_exit else R.drawable.ic_fullscreen ) @@ -841,7 +1100,7 @@ class PlayerActivity : AppCompatActivity() { confirmDeleteDownload() } DownloadCenter.hasActiveDownload(this, entry.url) -> { - Toast.makeText(this, R.string.download_status_running, Toast.LENGTH_SHORT).show() + com.sparktube.app.ui.MainActivity.openDownloads(this) } else -> showDownloadSheet() } @@ -997,6 +1256,8 @@ class PlayerActivity : AppCompatActivity() { private val downloadPoll = object : Runnable { override fun run() { + binding.actionTimerLabel.text = com.sparktube.app.ui.music.SleepTimerSheet.buttonLabel(this@PlayerActivity) + binding.actionTimer.contentDescription = com.sparktube.app.ui.music.SleepTimerSheet.accessibilityLabel(this@PlayerActivity) val entry = PlaybackCenter.currentEntry if (entry != null && !isFinishing) { // Keep the registry in sync with the built-in download @@ -1078,10 +1339,7 @@ class PlayerActivity : AppCompatActivity() { private fun retry() { binding.errorView.isVisible = false - val entry = PlaybackCenter.currentEntry - if (entry != null) { - PlaybackCenter.playVideo(entry) - } + PlaybackCenter.retryCurrentVideo() } // ----- Bottom sheets ----- diff --git a/app/src/main/java/com/sparktube/app/ui/player/PlayerGestureMath.kt b/app/src/main/java/com/sparktube/app/ui/player/PlayerGestureMath.kt new file mode 100644 index 0000000..e49ffdd --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/player/PlayerGestureMath.kt @@ -0,0 +1,48 @@ +package com.sparktube.app.ui.player + +/** Pure calculations shared by touch handling and local JVM regression tests. */ +internal object PlayerGestureMath { + enum class Axis { NONE, HORIZONTAL, VERTICAL } + + /** Intentional surface seeks work in either orientation, never across a rotation. */ + fun canSeekVideoSurface(startedInLandscape: Boolean, isLandscape: Boolean): Boolean = + startedInLandscape == isLandscape + + /** Do not promote an unrelated diagonal portrait drag into a surface seek. */ + fun rejectAmbiguousPortraitMovement(startedInLandscape: Boolean, dx: Float, dy: Float, slop: Float): Boolean = + !startedInLandscape && maxOf(kotlin.math.abs(dx), kotlin.math.abs(dy)) >= slop && + chooseAxis(dx, dy, slop) == Axis.NONE + + /** Lock only after meaningful movement, preferring the clearly dominant axis. */ + fun chooseAxis(dx: Float, dy: Float, slop: Float, dominance: Float = 1.3f): Axis { + val x = kotlin.math.abs(dx) + val y = kotlin.math.abs(dy) + if (maxOf(x, y) < slop) return Axis.NONE + return when { + x > y * dominance -> Axis.HORIZONTAL + y > x * dominance -> Axis.VERTICAL + else -> Axis.NONE + } + } + + /** Horizontal seek: 120 dp of travel corresponds to a 10-second seek. */ + fun horizontalSeekTarget(positionMs: Long, durationMs: Long, deltaX: Float, pixelsPerTenSeconds: Float): Long { + if (durationMs <= 0L || pixelsPerTenSeconds <= 0f) return positionMs.coerceAtLeast(0L) + val current = positionMs.coerceIn(0L, durationMs) + val delta = (deltaX.toDouble() / pixelsPerTenSeconds * 10_000.0).toLong() + return (current.toDouble() + delta.toDouble()).coerceIn(0.0, durationMs.toDouble()).toLong() + } + + fun seekTarget(positionMs: Long, durationMs: Long, forward: Boolean): Long { + if (durationMs <= 0L) return positionMs.coerceAtLeast(0L) + val current = positionMs.coerceIn(0L, durationMs) + return if (forward) current + minOf(10_000L, durationMs - current) + else (current - 10_000L).coerceAtLeast(0L) + } + + fun volume(initial: Int, fraction: Float, max: Int): Int = + (initial + fraction * max.coerceAtLeast(0)).toInt().coerceIn(0, max.coerceAtLeast(0)) + + fun brightness(initial: Float, fraction: Float): Float = + (initial + fraction).coerceIn(0.01f, 1f) +} diff --git a/app/src/main/java/com/sparktube/app/ui/player/SeekBarDragGate.kt b/app/src/main/java/com/sparktube/app/ui/player/SeekBarDragGate.kt new file mode 100644 index 0000000..1a23e24 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/player/SeekBarDragGate.kt @@ -0,0 +1,33 @@ +package com.sparktube.app.ui.player + +/** Touch-only gate: taps never reach Media3, a clear horizontal drag does. */ +internal class SeekBarDragGate { + enum class State { IDLE, PENDING, DRAGGING, REJECTED } + var state = State.IDLE + private set + private var startX = 0f + private var startY = 0f + + fun down(x: Float, y: Float, onVisibleBar: Boolean = true) { + startX = x + startY = y + state = if (onVisibleBar) State.PENDING else State.REJECTED + } + + fun move(x: Float, y: Float, slop: Float): Boolean { + if (state == State.PENDING) { + val dx = x - startX + val dy = y - startY + // Once meaningful non-horizontal movement starts, this sequence + // cannot become a scrub later by wandering sideways into the bar. + if (maxOf(kotlin.math.abs(dx), kotlin.math.abs(dy)) >= slop) { + state = if (PlayerGestureMath.chooseAxis(dx, dy, slop) == PlayerGestureMath.Axis.HORIZONTAL) + State.DRAGGING else State.REJECTED + } + } + return state == State.DRAGGING + } + + fun reset() { state = State.IDLE } + fun reject() { state = State.REJECTED } +} diff --git a/app/src/main/java/com/sparktube/app/ui/player/SeekBarTouchGeometry.kt b/app/src/main/java/com/sparktube/app/ui/player/SeekBarTouchGeometry.kt new file mode 100644 index 0000000..d99e131 --- /dev/null +++ b/app/src/main/java/com/sparktube/app/ui/player/SeekBarTouchGeometry.kt @@ -0,0 +1,29 @@ +package com.sparktube.app.ui.player + +/** Pixel geometry of the painted Media3 track, not its larger invisible touch target. */ +internal data class SeekBarTouchGeometry( + val left: Int, val top: Int, val right: Int, val bottom: Int +) { + fun contains(x: Float, y: Float, positionMs: Long, durationMs: Long, thumbRadius: Int): Boolean { + if (right <= left || bottom <= top || durationMs <= 0 || !x.isFinite() || !y.isFinite()) return false + if (x >= left && x < right && y >= top && y < bottom) return true + if (thumbRadius <= 0) return false + // Match DefaultTimeBar's integer progress position and circle center. + val thumbX = left + ((right - left).toLong() * positionMs.coerceIn(0, durationMs) / durationMs).toInt() + val thumbY = (top + bottom) / 2 + val dx = x - thumbX + val dy = y - thumbY + return dx * dx + dy * dy <= thumbRadius.toFloat() * thumbRadius + } + + companion object { + /** Matches DefaultTimeBar 1.3.1 onLayout, including bottom gravity and thumb inset. */ + fun layout(width: Int, height: Int, paddingLeft: Int, paddingRight: Int, + paddingBottom: Int, barHeight: Int, scrubberPadding: Int, bottomGravity: Boolean): SeekBarTouchGeometry { + val top = if (bottomGravity) height - paddingBottom - barHeight - + maxOf(scrubberPadding - barHeight / 2, 0) else (height - barHeight) / 2 + return SeekBarTouchGeometry(paddingLeft + scrubberPadding, top, + width - paddingRight - scrubberPadding, top + barHeight) + } + } +} diff --git a/app/src/main/res/drawable/bg_mode_switch.xml b/app/src/main/res/drawable/bg_mode_switch.xml new file mode 100644 index 0000000..7aef0f6 --- /dev/null +++ b/app/src/main/res/drawable/bg_mode_switch.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_mode_switch_active.xml b/app/src/main/res/drawable/bg_mode_switch_active.xml new file mode 100644 index 0000000..36a83de --- /dev/null +++ b/app/src/main/res/drawable/bg_mode_switch_active.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_mode_switch_hit_area.xml b/app/src/main/res/drawable/bg_mode_switch_hit_area.xml new file mode 100644 index 0000000..fc8d6de --- /dev/null +++ b/app/src/main/res/drawable/bg_mode_switch_hit_area.xml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/bg_transport_circle.xml b/app/src/main/res/drawable/bg_transport_circle.xml new file mode 100644 index 0000000..df428b0 --- /dev/null +++ b/app/src/main/res/drawable/bg_transport_circle.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_download_more.xml b/app/src/main/res/drawable/ic_download_more.xml new file mode 100644 index 0000000..a711499 --- /dev/null +++ b/app/src/main/res/drawable/ic_download_more.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/ic_sleep_timer.xml b/app/src/main/res/drawable/ic_sleep_timer.xml new file mode 100644 index 0000000..4033e5a --- /dev/null +++ b/app/src/main/res/drawable/ic_sleep_timer.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/layout/activity_now_playing.xml b/app/src/main/res/layout/activity_now_playing.xml index f63f681..62ef000 100644 --- a/app/src/main/res/layout/activity_now_playing.xml +++ b/app/src/main/res/layout/activity_now_playing.xml @@ -35,10 +35,52 @@ android:textSize="11sp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@id/backButton" - app:layout_constraintEnd_toStartOf="@id/queueButton" + app:layout_constraintEnd_toStartOf="@id/modeSwitch" app:layout_constraintStart_toEndOf="@id/backButton" app:layout_constraintTop_toTopOf="@id/backButton" /> + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + android:importantForAccessibility="no" + app:layout_constraintStart_toStartOf="@id/playerView" + app:layout_constraintEnd_toEndOf="@id/playerView" + app:layout_constraintTop_toTopOf="@id/playerView" + app:layout_constraintBottom_toBottomOf="@id/playerView" /> @@ -274,17 +377,18 @@ + android:id="@+id/actionTimerLabel" + android:text="@string/sleep_timer_button" /> + android:text="@string/retry" /> diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 0efd304..2692f72 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -37,14 +37,6 @@ android:layout_height="1dp" android:layout_weight="1" /> - - + android:paddingBottom="12dp"> - - + + +