Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
36bb7ea
Add Chromecast-like REST API for remote control
akshaynexus Jun 10, 2026
2e5578f
Add WebSocket support for real-time player state updates
akshaynexus Jun 10, 2026
000778a
Add home theater controller + WebSocket theater state broadcast + pow…
akshaynexus Jun 10, 2026
34cbfd0
Fix theater state: fresh reads from hardware + dumpsys fallback
akshaynexus Jun 10, 2026
2181907
Add Home Theater debug test activity
akshaynexus Jun 10, 2026
2deabbc
Add Home Theater debug test activity + fix build with Java 17
akshaynexus Jun 10, 2026
77968bf
docs: add ADB bridge requirements for HDMI CEC theater endpoints
akshaynexus Jun 10, 2026
425706d
refactor: strip CEC endpoints from app, keep volume/mute/power/audio_…
akshaynexus Jun 10, 2026
42308b4
fix: catch JSONException in debug activity getState toString
akshaynexus Jun 10, 2026
67b6756
fixes and updates to api server and websocket
akshaynexus Jun 11, 2026
2227581
Fix REST API rate-limiting, thread-safety, and shutdown bugs
akshaynexus Jun 11, 2026
6734ea8
Add open-connection mode, fix PUT request bodies
akshaynexus Jun 12, 2026
ad4e99b
Keep WebSocket clients alive with server-side pings
akshaynexus Jun 12, 2026
f5924bf
Run all player-mutating API commands on the main thread
akshaynexus Jun 12, 2026
d18a444
Persist API-set player volume so it survives video changes
akshaynexus Jun 12, 2026
b68b5f1
Debounce rapid skips, ship high-res thumbnails
akshaynexus Jun 12, 2026
5c901e8
Make absolute TV volume work with CEC audio systems
akshaynexus Jun 12, 2026
24a53cd
Tune CEC volume ramp from live HT-A9 measurements
akshaynexus Jun 12, 2026
48f91fe
Persist internal player volume changes
akshaynexus Jun 12, 2026
4cd64a0
Fix CEC volume ramp retargeting, player volume ratchet; add recommend…
akshaynexus Jun 12, 2026
29e43cf
Allow playing suggestions by video ID; doc updates
akshaynexus Jun 12, 2026
35d47ca
Add queue shuffle/move/reorder, bulk playlist enqueue, and PIP to rem…
akshaynexus Jun 12, 2026
7442692
Add OpenAPI 3.0.3 spec for the remote control API
akshaynexus Jun 12, 2026
322e4c5
Fix player volume drifting down on each video change
akshaynexus Jun 12, 2026
c343ae9
Add search results endpoint to remote API
akshaynexus Jun 12, 2026
c425805
Merge branch 'master' into akver
akshaynexus Jun 12, 2026
de3eaba
Add chapters endpoint to remote API
akshaynexus Jun 12, 2026
90a442e
Restore upstream player volume behavior
akshaynexus Jun 13, 2026
9f894b1
Keep full player volume at max baseline
akshaynexus Jun 13, 2026
5cc24f2
Set default player volume for remote control
akshaynexus Jun 15, 2026
3fc6378
Migrate default player volume baseline
akshaynexus Jun 15, 2026
6a22bbe
Fix remote API metadata push, suggestions updates, and default volume
akshaynexus Jun 15, 2026
968cb7a
Merge branch 'yuliskov:master' into akver
akshaynexus Jun 15, 2026
ea6e982
Refactor remote API/WebSocket: cut duplication, add PIP toggle/status…
akshaynexus Jun 26, 2026
bdb0b65
Fix D-pad remote API: stop crash and actually navigate focus
akshaynexus Jun 28, 2026
75d1c8b
Remote API: report titles for videos opened by id (player/queue)
akshaynexus Jun 28, 2026
e61585d
Remote API: make Up Next reliable and lead with the actual next video
akshaynexus Jun 28, 2026
b99c0ac
Remote API: Up Next = reliable suggestion rows, minus the video's own…
akshaynexus Jun 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# AGENTS.md

## Build

### APK Build Command
```bash
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./gradlew :smarttubetv:assembleStstableDebug
```

### Java Version Fix
AGP 7.4.2 + Java 21 causes a D8 NPE during dexing (`VideoCardPresenter$2.class`). Must use Java 17:
```bash
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
```

### Module Compilation (fast check, no APK)
```bash
./gradlew :common:compileStstableDebugJavaWithJavac
```

### Install to TV
```bash
adb connect <ip>:<port>
adb install -r <apk>
# If signature mismatch: adb uninstall <package> && adb install <apk>
```

### Package Name
- `org.smarttube.stable` (stable flavor)
9 changes: 8 additions & 1 deletion common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ android {

compileSdkVersion project.properties.compileSdkVersion
buildToolsVersion project.properties.buildToolsVersion
testOptions.unitTests.includeAndroidResources = true
testOptions {
unitTests.includeAndroidResources = true
unitTests.returnDefaultValues = true
}

defaultConfig {
minSdkVersion project.properties.minSdkVersion
Expand Down Expand Up @@ -57,6 +60,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:' + appCompatXVersion
testImplementation 'junit:junit:' + junitVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation 'org.mockito:mockito-core:' + mockitoVersion

implementation project(':sharedutils')
implementation project(':fragment-1.1.0')
Expand Down Expand Up @@ -103,4 +107,7 @@ dependencies {
implementation project(':appupdatechecker2')

implementation project(':slidableactivity') // https://github.com/r0adkll/Slidr

implementation 'org.nanohttpd:nanohttpd:2.3.1'
implementation 'org.nanohttpd:nanohttpd-websocket:2.3.1'
}
3 changes: 3 additions & 0 deletions common/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.liskovsoft.smartyoutubetv2.common">
<uses-sdk tools:overrideLibrary="androidx.test.espresso.idling.resource,androidx.test.monitor,androidx.test.core,androidx.test.ext.junit,androidx.test.ext.truth,androidx.test.runner,androidx.test.rules,androidx.test.espresso" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Android 14 (API 34) -->
<!-- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" /> -->
Expand All @@ -19,6 +21,7 @@
<!-- Starting targetSDK 33+, foreground services require explicit permissions in the manifest. -->
<!-- NOTE: only works with 'mediaPlayback' type service. Others types will fail. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.INTERNET" />

<queries>
<!-- Android 11+: access package info (works only for specified apps) -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class Playlist {
private final List<Video> mPlaylist;
private final List<Video> mSyncedItems;
private int mCurrentIndex;
private volatile int mGeneration;
private static Playlist sInstance;

private Playlist() {
Expand All @@ -39,18 +40,57 @@ public static Playlist instance() {
public void clear() {
mPlaylist.clear();
mCurrentIndex = -1;
mGeneration++;
}

public void clearPosition() {
mCurrentIndex = -1;
}

/**
* Shuffles the playlist while keeping the currently-playing item as current.
*/
public void shuffle() {
if (mPlaylist.isEmpty()) {
return;
}
Video current = getCurrent();
Collections.shuffle(mPlaylist);
if (current != null) {
int newIndex = mPlaylist.indexOf(current);
mCurrentIndex = newIndex >= 0 ? newIndex : 0;
} else {
mCurrentIndex = 0;
}
mGeneration++;
}

/**
* Reorders one item in the playlist while keeping the current pointer correct.
*/
public void move(int fromIndex, int toIndex) {
if (mPlaylist.isEmpty() || fromIndex == toIndex) {
return;
}
if (fromIndex < 0 || fromIndex >= mPlaylist.size() || toIndex < 0 || toIndex >= mPlaylist.size()) {
return;
}
Video current = getCurrent();
Video moved = mPlaylist.remove(fromIndex);
mPlaylist.add(toIndex, moved);
if (current != null) {
mCurrentIndex = mPlaylist.indexOf(current);
}
mGeneration++;
}

/**
* Used to sync list with remotely added items
*/
public void addAll(List<Video> videos) {
mPlaylist.removeAll(videos);
mPlaylist.addAll(videos);
mGeneration++;
}

/**
Expand Down Expand Up @@ -87,6 +127,8 @@ public void add(Video video) {
// In this case remove all next items.
trimPlaylist();
stripPrevItem();

mGeneration++;
}

public void next(Video video) {
Expand All @@ -109,6 +151,8 @@ public void next(Video video) {
// In this case remove all next items.
trimPlaylist();
stripPrevItem();

mGeneration++;
}

public void remove(Video video) {
Expand Down Expand Up @@ -140,6 +184,8 @@ public void remove(Video video) {
mCurrentIndex = mPlaylist.size() - 1;
}
}

mGeneration++;
}

public boolean contains(Video video) {
Expand Down Expand Up @@ -320,6 +366,10 @@ private void replace(Video origin, Video newItem) {
}
}

public int getGeneration() {
return mGeneration;
}

public void onNewSession() {
mSyncedItems.clear();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ public class SuggestionsController extends BasePlayerController {
private Video mNextSectionVideo;
private int mFocusCount;
private int mNextRetryCount;
private List<ChapterItem> mChapters;
// Written on the main thread (metadata load), but also read from NanoHTTPD worker
// threads via the remote API (RemoteApiBridge.getChapters) — volatile for visibility.
private volatile List<ChapterItem> mChapters;
private final Runnable mChapterHandler = this::startChapterNotificationServiceIfNeededInt;
private static final int MAX_PLAYLIST_CONTINUATIONS = 20;
private static final int CHAPTER_NOTIFICATION_Id = 565;
Expand Down Expand Up @@ -354,6 +356,9 @@ private void updateSuggestions(MediaItemMetadata mediaItemMetadata, Video video)

// After video suggestions
callListener(mediaItemMetadata);

// Notify remote API clients that suggestions have been updated
com.liskovsoft.smartyoutubetv2.common.misc.remoteapi.RemoteApiBridge.notifySuggestionsUpdated();
}

private void appendSuggestions(Video video, MediaItemMetadata mediaItemMetadata) {
Expand Down Expand Up @@ -546,6 +551,14 @@ private void startChapterNotificationServiceIfNeededInt() {
}
}

/**
* Chapters of the currently playing video (parsed from YouTube metadata),
* or null when the video has none / nothing is playing. Used by the remote API.
*/
public List<ChapterItem> getChapters() {
return mChapters;
}

private void appendChaptersIfNeeded(MediaItemMetadata mediaItemMetadata) {
mChapters = mediaItemMetadata.getChapters();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -613,14 +613,8 @@ private void restoreVolume() {

float newVolume = getPlayerData().getPlayerVolume();

if (getPlayerTweaksData().isPlayerAutoVolumeEnabled()) {
//newVolume *= getVideo().volume;
//newVolume = getVideo().volume;
if (newVolume < 1f) {
newVolume *= getVideo().volume;
} else {
newVolume = getVideo().volume;
}
if (getPlayerTweaksData().isPlayerAutoVolumeEnabled() && newVolume < 1f) {
newVolume *= getVideo().volume;
}

if (getVideo().isShorts) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.liskovsoft.smartyoutubetv2.common.app.presenters.dialogs.menu.VideoMenuPresenter;
import com.liskovsoft.smartyoutubetv2.common.app.views.PlaybackView;
import com.liskovsoft.smartyoutubetv2.common.exoplayer.selector.FormatItem;
import com.liskovsoft.smartyoutubetv2.common.misc.remoteapi.RemoteApiBridge;
import com.liskovsoft.smartyoutubetv2.common.utils.Utils;
import com.liskovsoft.smartyoutubetv2.common.utils.Utils.ChainProcessor;
import com.liskovsoft.smartyoutubetv2.common.utils.Utils.Processor;
Expand Down Expand Up @@ -61,6 +62,7 @@ private PlaybackPresenter(Context context) {
mEventListeners.add(new ErrorFixerController());
mEventListeners.add(new PlayerUIController());
mEventListeners.add(new RemoteController(context));
RemoteApiBridge.init(this);
mEventListeners.add(new SponsorBlockController());
mEventListeners.add(new AutoFrameRateController());
mEventListeners.add(new HQDialogController());
Expand Down
Loading