Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ SmartTube is a free and open-source media client for Android TVs and TV boxes. I
- HDR compatibility
- View live chat
- Customizable buttons
- Start mix (radio-style infinite playback)
- Does not require Google Services
- Helpful international community

Expand Down Expand Up @@ -172,6 +173,11 @@ SmartTube supports playing videos in PiP mode. This needs to be enabled under _S
You can adjust the playback speed pressing the speed-indicator icon (gauge) in the top row of the player. This is remembered across videos. Some speeds may case frame drops, this is a known issue.


### Start Mix

You can start a mix (radio-style infinite playback) from any video. Long-press a video card and select _Start mix_ from the context menu, or press the mix button in the player toolbar while watching. SmartTube uses YouTube's algorithmic radio to queue a continuous stream of related videos.


### Voice Search

To enable global voice search, an additional app must be installed alongside SmartTube. This _bridge app_ allows the system voice search to open SmartTube instead of the default YouTube app. For this to work, you must uninstall the original YouTube app. We know this sucks, but you can always reinstall it if you change your mind. The _bridge app_ will not show up in your launcher and you cannot launch it directly; it is only used internally by the system's voice search. On some devices, you need to explicitly say "Youtube" when searching (e.g. say "youtube cute cats" instead of just "cute cats").
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.liskovsoft.sharedutils.rx.RxHelper;
import com.liskovsoft.smartyoutubetv2.common.R;
import com.liskovsoft.smartyoutubetv2.common.app.models.data.Video;
import com.liskovsoft.smartyoutubetv2.common.app.models.data.Playlist;
import com.liskovsoft.smartyoutubetv2.common.app.models.data.VideoGroup;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.BasePlayerController;
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.manager.PlayerConstants;
Expand All @@ -28,6 +29,7 @@
import com.liskovsoft.smartyoutubetv2.common.app.models.playback.ui.UiOptionItem;
import com.liskovsoft.smartyoutubetv2.common.app.presenters.AppDialogPresenter;
import com.liskovsoft.smartyoutubetv2.common.app.presenters.ChannelPresenter;
import com.liskovsoft.smartyoutubetv2.common.app.presenters.PlaybackPresenter;
import com.liskovsoft.smartyoutubetv2.common.app.presenters.SearchPresenter;
import com.liskovsoft.smartyoutubetv2.common.app.presenters.dialogs.menu.VideoMenuPresenter;
import com.liskovsoft.smartyoutubetv2.common.app.presenters.dialogs.menu.VideoMenuPresenter.VideoMenuCallback;
Expand Down Expand Up @@ -354,6 +356,7 @@ private void resetButtonStates() {
getPlayer().setButtonState(R.id.action_video_speed, PlayerUI.BUTTON_OFF);
getPlayer().setButtonState(R.id.action_chat, PlayerUI.BUTTON_OFF);
getPlayer().setButtonState(R.id.action_subscribe, PlayerUI.BUTTON_OFF);
getPlayer().setButtonState(R.id.action_start_mix, PlayerUI.BUTTON_OFF);
}

@Override
Expand Down Expand Up @@ -619,6 +622,8 @@ public void onButtonClicked(int buttonId, int buttonState) {
onDislikeClicked(buttonState);
} else if (buttonId == R.id.action_thumbs_up) {
onLikeClicked(buttonState);
} else if (buttonId == R.id.action_start_mix) {
onStartMixClicked();
}
}

Expand Down Expand Up @@ -996,6 +1001,23 @@ private void onFlip() {
getPlayerData().setVideoFlipEnabled(newFlipEnabled);
}

private void onStartMixClicked() {
Video video = getVideo();
if (video == null || getPlayer() == null) {
return;
}

Playlist.instance().clear();

video.playlistId = "RD" + video.videoId;
video.playlistIndex = 0;
video.playlistParams = null;

mSuggestionsController.loadSuggestions(video);

getPlayer().setButtonState(R.id.action_start_mix, PlayerUI.BUTTON_ON);
}

private void onSubscribe(int buttonState) {
if (getPlayer() == null || getVideo() == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ public void openVideo(Video video) {
mIsEmbedPlayerStarted = false;
}

public void startMix(Video source) {
if (source == null) {
return;
}

Playlist.instance().clear();

Video mixVideo = Video.from(source);
mixVideo.playlistId = "RD" + source.videoId;
mixVideo.playlistIndex = 0;
mixVideo.playlistParams = null;

openVideo(mixVideo);
}

public Video getVideo() {
return mVideo != null ? mVideo.get() : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class VideoMenuPresenter extends BaseMenuPresenter {
private boolean mIsPlaylistOrderButtonEnabled;
private boolean mIsStreamReminderButtonEnabled;
private boolean mIsMarkAsWatchedButtonEnabled;
private boolean mIsStartMixButtonEnabled;
private VideoMenuCallback mCallback;
private List<PlaylistInfo> mPlaylistInfos;
private final Map<Long, MenuAction> mMenuMapping = new HashMap<>();
Expand Down Expand Up @@ -656,6 +657,20 @@ private void appendPlayFromStartButton() {
));
}

private void appendStartMixButton() {
if (!mIsStartMixButtonEnabled || mVideo == null || !mVideo.hasVideo()) {
return;
}

mDialogPresenter.appendSingleButton(
UiOptionItem.from(getContext().getString(R.string.start_mix),
optionItem -> {
PlaybackPresenter.instance(getContext()).startMix(mVideo);
mDialogPresenter.closeDialog();
}
));
}

private void showLongTextDialog(String description) {
mDialogPresenter.appendLongTextCategory(mVideo.getTitle(), UiOptionItem.from(description));
mDialogPresenter.showDialog(mVideo.getTitle());
Expand Down Expand Up @@ -971,6 +986,7 @@ protected void updateEnabledMenuItems() {
mIsPlaylistOrderButtonEnabled = mainUIData.isMenuItemEnabled(MainUIData.MENU_ITEM_PLAYLIST_ORDER);
mIsMarkAsWatchedButtonEnabled = mainUIData.isMenuItemEnabled(MainUIData.MENU_ITEM_MARK_AS_WATCHED);
mIsOpenCommentsButtonEnabled = mainUIData.isMenuItemEnabled(MainUIData.MENU_ITEM_OPEN_COMMENTS);
mIsStartMixButtonEnabled = mainUIData.isMenuItemEnabled(MainUIData.MENU_ITEM_START_MIX);
}

private void initMenuMapping() {
Expand All @@ -979,6 +995,7 @@ private void initMenuMapping() {
mMenuMapping.put(MainUIData.MENU_ITEM_PLAY_VIDEO, new MenuAction(this::appendPlayVideoButton, false));
mMenuMapping.put(MainUIData.MENU_ITEM_PLAY_VIDEO_INCOGNITO, new MenuAction(this::appendPlayVideoIncognitoButton, false));
mMenuMapping.put(MainUIData.MENU_ITEM_PLAY_FROM_START, new MenuAction(this::appendPlayFromStartButton, false));
mMenuMapping.put(MainUIData.MENU_ITEM_START_MIX, new MenuAction(this::appendStartMixButton, false));
mMenuMapping.put(MainUIData.MENU_ITEM_REMOVE_FROM_HISTORY, new MenuAction(this::appendRemoveFromHistoryButton, false));
mMenuMapping.put(MainUIData.MENU_ITEM_STREAM_REMINDER, new MenuAction(this::appendStreamReminderButton, false));
mMenuMapping.put(MainUIData.MENU_ITEM_RECENT_PLAYLIST, new MenuAction(this::appendAddToRecentPlaylistButton, false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@ private Map<Long, Integer> getMenuNames() {
menuNames.put(MainUIData.MENU_ITEM_OPEN_COMMENTS, R.string.open_comments);
menuNames.put(MainUIData.MENU_ITEM_OPEN_PLAYLIST, R.string.open_playlist);
menuNames.put(MainUIData.MENU_ITEM_BLOCK_CHANNEL, R.string.dialog_block_channel);
menuNames.put(MainUIData.MENU_ITEM_START_MIX, R.string.start_mix);

for (ContextMenuProvider provider : new ContextMenuManager(getContext()).getProviders()) {
menuNames.put(provider.getId(), provider.getTitleResId());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,8 @@ private void appendPlayerButtonsCategory(AppDialogPresenter settingsPresenter) {
{R.string.action_repeat_mode, PlayerTweaksData.PLAYER_BUTTON_REPEAT_MODE},
{R.string.action_next, PlayerTweaksData.PLAYER_BUTTON_NEXT},
{R.string.action_previous, PlayerTweaksData.PLAYER_BUTTON_PREVIOUS},
{R.string.playback_settings, PlayerTweaksData.PLAYER_BUTTON_HIGH_QUALITY}
{R.string.playback_settings, PlayerTweaksData.PLAYER_BUTTON_HIGH_QUALITY},
{R.string.start_mix, PlayerTweaksData.PLAYER_BUTTON_START_MIX}
}) {
options.add(UiOptionItem.from(getContext().getString(pair[0]), optionItem -> {
if (optionItem.isSelected()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public class MainUIData extends DataChangeBase implements ProfileChangeListener
public static final long MENU_ITEM_PLAY_FROM_START = 1L << 36;
public static final long MENU_ITEM_BLOCK_CHANNEL = 1L << 37;
public static final long MENU_ITEM_ADD_TO_WATCH_LATER = 1L << 38;
public static final long MENU_ITEM_START_MIX = 1L << 39;
public static final int TOP_BUTTON_BROWSE_ACCOUNTS = 1;
public static final int TOP_BUTTON_CHANGE_LANGUAGE = 1 << 1;
public static final int TOP_BUTTON_SEARCH = 1 << 2;
Expand All @@ -78,7 +79,7 @@ public class MainUIData extends DataChangeBase implements ProfileChangeListener
| MENU_ITEM_RENAME_SECTION | MENU_ITEM_SAVE_REMOVE_PLAYLIST | MENU_ITEM_ADD_TO_PLAYLIST | MENU_ITEM_CREATE_PLAYLIST
| MENU_ITEM_RENAME_PLAYLIST | MENU_ITEM_ADD_TO_NEW_PLAYLIST | MENU_ITEM_STREAM_REMINDER | MENU_ITEM_PLAYLIST_ORDER
| MENU_ITEM_OPEN_CHANNEL | MENU_ITEM_REMOVE_FROM_SUBSCRIPTIONS | MENU_ITEM_PLAY_NEXT | MENU_ITEM_OPEN_PLAYLIST
| MENU_ITEM_SUBSCRIBE | MENU_ITEM_CLEAR_HISTORY | MENU_ITEM_ADD_TO_WATCH_LATER;
| MENU_ITEM_SUBSCRIBE | MENU_ITEM_CLEAR_HISTORY | MENU_ITEM_ADD_TO_WATCH_LATER | MENU_ITEM_START_MIX;
private static final Long[] MENU_ITEM_DEFAULT_ORDER = {
MENU_ITEM_EXIT_FROM_PIP, MENU_ITEM_PLAY_VIDEO, MENU_ITEM_PLAY_VIDEO_INCOGNITO, MENU_ITEM_PLAY_FROM_START, MENU_ITEM_REMOVE_FROM_HISTORY,
MENU_ITEM_STREAM_REMINDER, MENU_ITEM_RECENT_PLAYLIST, MENU_ITEM_ADD_TO_WATCH_LATER, MENU_ITEM_ADD_TO_PLAYLIST, MENU_ITEM_CREATE_PLAYLIST,
Expand All @@ -87,7 +88,8 @@ public class MainUIData extends DataChangeBase implements ProfileChangeListener
MENU_ITEM_SHOW_QUEUE, MENU_ITEM_OPEN_CHANNEL, MENU_ITEM_OPEN_PLAYLIST, MENU_ITEM_SUBSCRIBE, MENU_ITEM_EXCLUDE_FROM_CONTENT_BLOCK,
MENU_ITEM_PIN_TO_SIDEBAR, MENU_ITEM_SAVE_REMOVE_PLAYLIST, MENU_ITEM_OPEN_DESCRIPTION, MENU_ITEM_OPEN_COMMENTS, MENU_ITEM_SHARE_LINK,
MENU_ITEM_SHARE_EMBED_LINK, MENU_ITEM_SHARE_QR_LINK, MENU_ITEM_SELECT_ACCOUNT, MENU_ITEM_TOGGLE_HISTORY, MENU_ITEM_CLEAR_HISTORY,
MENU_ITEM_MOVE_SECTION_UP, MENU_ITEM_MOVE_SECTION_DOWN, MENU_ITEM_RENAME_SECTION, MENU_ITEM_UPDATE_CHECK
MENU_ITEM_MOVE_SECTION_UP, MENU_ITEM_MOVE_SECTION_DOWN, MENU_ITEM_RENAME_SECTION, MENU_ITEM_UPDATE_CHECK,
MENU_ITEM_START_MIX
};
public static final long UI_TWEAK_DEFAULT = UI_TWEAK_ROUNDED_CORNERS;
@SuppressLint("StaticFieldLeak")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ public class PlayerTweaksData implements ProfileChangeListener {
public static final int PLAYER_BUTTON_SOUND_OFF = 1 << 25;
public static final int PLAYER_BUTTON_AFR = 1 << 26;
public static final int PLAYER_BUTTON_VIDEO_FLIP = 1 << 27;
public static final int PLAYER_BUTTON_START_MIX = 1 << 28;
public static final int PLAYER_BUTTON_DEFAULT = PLAYER_BUTTON_SEARCH | PLAYER_BUTTON_PIP | PLAYER_BUTTON_SCREEN_DIMMING | PLAYER_BUTTON_VIDEO_SPEED |
PLAYER_BUTTON_VIDEO_STATS | PLAYER_BUTTON_OPEN_CHANNEL | PLAYER_BUTTON_SUBTITLES | PLAYER_BUTTON_SUBSCRIBE |
PLAYER_BUTTON_LIKE | PLAYER_BUTTON_DISLIKE | PLAYER_BUTTON_ADD_TO_PLAYLIST | PLAYER_BUTTON_PLAY_PAUSE |
PLAYER_BUTTON_REPEAT_MODE | PLAYER_BUTTON_NEXT | PLAYER_BUTTON_PREVIOUS | PLAYER_BUTTON_HIGH_QUALITY |
PLAYER_BUTTON_VIDEO_INFO | PLAYER_BUTTON_CHAT;
PLAYER_BUTTON_VIDEO_INFO | PLAYER_BUTTON_CHAT | PLAYER_BUTTON_START_MIX;
public static final int DNS_TYPE_SYSTEM = GlobalPreferences.DNS_TYPE_SYSTEM;
public static final int DNS_TYPE_IPV4 = GlobalPreferences.DNS_TYPE_IPV4;
public static final int DNS_TYPE_GOOGLE = GlobalPreferences.DNS_TYPE_GOOGLE;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions common/src/main/res/values/ids.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@
<item name="action_afr" type="id"/>
<item name="channel_new_content" type="id"/>
<item name="linkify_click_handler" type="id"/>
<item name="action_start_mix" type="id"/>
</resources>
1 change: 1 addition & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@
<string name="volume_boost_warning">Any settings over the limit can potentially damage speakers</string>
<string name="play_video_incognito">Play incognito</string>
<string name="play_from_start">Play from start</string>
<string name="start_mix">Start mix</string>
<string name="header_kids_home">Kids</string>
<string name="player_section_playlist">Use current section contents as a playlist</string>
<string name="header_trending">Trending</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.liskovsoft.smartyoutubetv2.tv.ui.playback.actions;

import android.content.Context;
import com.liskovsoft.smartyoutubetv2.tv.R;

/**
* An action for displaying start mix states.
*/
public class StartMixAction extends TwoStateAction {
public StartMixAction(Context context) {
super(context, R.id.action_start_mix, R.drawable.action_start_mix);

String[] labels = new String[2];
labels[INDEX_OFF] = context.getString(R.string.start_mix);
labels[INDEX_ON] = context.getString(R.string.start_mix);
setLabels(labels);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.liskovsoft.smartyoutubetv2.tv.ui.playback.actions.SeekIntervalAction;
import com.liskovsoft.smartyoutubetv2.tv.ui.playback.actions.ShareAction;
import com.liskovsoft.smartyoutubetv2.tv.ui.playback.actions.SoundOffAction;
import com.liskovsoft.smartyoutubetv2.tv.ui.playback.actions.StartMixAction;
import com.liskovsoft.smartyoutubetv2.tv.ui.playback.actions.VideoInfoAction;
import com.liskovsoft.smartyoutubetv2.tv.ui.playback.actions.PipAction;
import com.liskovsoft.smartyoutubetv2.tv.ui.playback.actions.PlaybackQueueAction;
Expand Down Expand Up @@ -134,6 +135,7 @@ public VideoPlayerGlue(
putAction(new RotateAction(context));
putAction(new FlipAction(context));
putAction(new SoundOffAction(context));
putAction(new StartMixAction(context));
}

@Override
Expand All @@ -157,6 +159,9 @@ protected void onCreatePrimaryActions(ArrayObjectAdapter adapter) {
if (mPlayerTweaksData.isPlayerButtonEnabled(PlayerTweaksData.PLAYER_BUTTON_REPEAT_MODE)) {
adapter.add(mActions.get(R.id.action_repeat));
}
if (mPlayerTweaksData.isPlayerButtonEnabled(PlayerTweaksData.PLAYER_BUTTON_START_MIX)) {
adapter.add(mActions.get(R.id.action_start_mix));
}
if (mPlayerTweaksData.isPlayerButtonEnabled(PlayerTweaksData.PLAYER_BUTTON_VIDEO_SPEED)) {
adapter.add(mActions.get(R.id.action_video_speed));
}
Expand Down