Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fixes and improvements #258

Merged
merged 7 commits into from
Aug 4, 2023
Merged
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
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
- background playback
- Live stream support
- Android TV ui
- Audio playback
- Video / audio download
- Video filtering
- Return YouTube dislikes

## Installation
The best way to install is to get it directly from the release page. Using [Obtainium](https://github.com/ImranR98/Obtainium) can help keeping the app up to date.
Expand All @@ -54,21 +58,27 @@ Or download the latest APK from the [Releases Section](https://github.com/lamari

## Screenshots
### Phone
[![Video list](./screenshots/video-list_small.png)](./screenshots/video-list.png)
[![Video details](./screenshots/video-details_small.png)](./screenshots/video-details.png)
[![Channel](./screenshots/channel_small.png)](./screenshots/channel.png)
[![Home](./screenshots/mobile-home_small.png)](./screenshots/mobile-home.png)
[![Video](./screenshots/mobile-video_small.png)](./screenshots/mobile-video.png)
[![Channel](./screenshots/mobile-channel_small.png)](./screenshots/mobile-channel_small.png)
[![Playlist](./screenshots/mobile-playlist_small.png)](./screenshots/mobile-playlist_small.png)

### Tablet

[![Video list](./screenshots/video-list-tablet_small.png)](./screenshots/video-list-tablet.png)
[![Video details](./screenshots/video-detail-tablet_small.png)](./screenshots/video-detail-tablet.png)
[![Channel](./screenshots/channel-tablet_small.png)](./screenshots/channel-tablet.png)
[![Home](./screenshots/tablet-home_small.png)](./screenshots/tablet-home.png)
[![Video](./screenshots/tablet-video_small.png)](./screenshots/tablet-video.png)
[![Channel](./screenshots/tablet-channel_small.png)](./screenshots/tablet-channel_small.png)
[![Playlist](./screenshots/tablet-playlist_small.png)](./screenshots/tablet-playlist_small.png)

### TV

[![Home](./screenshots/tv-home_small.png)](./screenshots/tv-home.png)
[![Home](./screenshots/tv-home-2_small.png)](./screenshots/tv-home-2.png)
[![Video](./screenshots/tv-video_small.png)](./screenshots/tv-video.png)
[![Video](./screenshots/tv-video-2_small.png)](./screenshots/tv-video-2.png)
[![Channel](./screenshots/tv-channel_small.png)](./screenshots/tv-channel_small.png)
[![Playlist](./screenshots/tv-playlist_small.png)](./screenshots/tv-playlist_small.png)
[![Playlist](./screenshots/tv-playlist-2_small.png)](./screenshots/tv-playlist_small-2.png)

## Facing an issue ?

Expand Down
17 changes: 17 additions & 0 deletions fastlane/metadata/android/en-US/changelogs/4010.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Mobile: Play audio only
Mobile: Playback notification is now working as expected
Mobile: Headset controls should now work
Mobile: Downloading a video will show a dialogue where user can select quality and audio only or not
Mobile: Links will now be parsed in comments as well
Mobile: clicking a video link in a video description and comments will now open the video in clipious instead of the browser
Mobile: clickable timestamp in video description and comments
Mobile: fix issue that prevented to infinite scroll in video lists if some videos were filtered by a remove from feed video filter
Mobile: Fix top bar not always black when the user selected Black background for oled
Mobile: Added triple dot menu on video in lists to properly show the existence of the video modal sheet
TV: Revamp playlist and video info screens
TV: fix issue that prevented to show all the types of videos a channel could have
TV: fix issue with playlist performance when there are a lot of videos
TV: add handling of remote media buttons (play/pause/skip etc...) add exponentially increasing fast forward / rewind if the remote is pressed rapidly or held for long
TV: Add duration of video in both thumbnail and video description
TV: Use full height of the screen to scroll through things and add a background when doing so to make it more readable when content is over the channel image
standardize video "metrics" (likes, duration, dislikes etc...) displayed on a video info across tv and mobile
2 changes: 1 addition & 1 deletion fastlane/metadata/android/en-US/full_description.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<p><i>Clipious</i>is an Android client for Invidious, the privacy focused YouTube front end.</p><p><b>Features:</b></p><ul><li>Use own or public server</li><li>Subscription management</li><li>SponsorBlock</li><li>Video view/progress tracking</li><li>User playlists</li><li>Background playback</li><li>Live stream support</li></ul>
<p><i>Clipious</i>is an Android client for Invidious, the privacy focused YouTube front end.</p><p><b>Features:</b></p><ul><li>Use own or public server</li><li>Subscription management</li><li>SponsorBlock</li><li>Video view/progress tracking</li><li>User playlists</li><li>Background playback</li><li>Live stream support</li><li>Android TV UI</li><li>Audio playback</li><li>Video / audio download</li><li>Video filtering</li><li>Return Youtube Dislikes</li></ul>
Binary file modified fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified fastlane/metadata/android/en-US/images/tenInchScreenshots/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified fastlane/metadata/android/en-US/images/tenInchScreenshots/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified fastlane/metadata/android/en-US/images/tvScreenshots/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified fastlane/metadata/android/en-US/images/tvScreenshots/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified fastlane/metadata/android/en-US/images/tvScreenshots/3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 10 additions & 8 deletions lib/controllers/audioPlayerController.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,23 +106,25 @@ class AudioPlayerController extends PlayerController {
}

@override
playVideo(bool offline) async {
playVideo(bool offline, {Duration? startAt}) async {
if (video != null || offlineVideo != null) {
disposeControllers();
initPlayer();
audioPosition = Duration.zero;
audioLength = Duration(seconds: offline ? offlineVideo!.videoLenthInSeconds : video!.lengthSeconds);
audioLength = Duration(seconds: offline ? offlineVideo!.lengthSeconds : video!.lengthSeconds);
loading = true;
MiniPlayerController.to()?.eventStream.add(MediaEvent(state: MediaState.loading));
try {
AudioSource? source;
Duration? startAt;

if (!offline) {
AdaptiveFormat? audio = video?.adaptiveFormats.where((element) => element.type.contains("audio")).sortByReversed((e) => int.parse(e.bitrate ?? "0")).first;
if (audio != null) {
double progress = db.getVideoProgress(video!.videoId);
if (progress > 0 && progress < 0.90) {
startAt = Duration(seconds: (video!.lengthSeconds * progress).floor());
if (startAt == null) {
double progress = db.getVideoProgress(video!.videoId);
if (progress > 0 && progress < 0.90) {
startAt = Duration(seconds: (video!.lengthSeconds * progress).floor());
}
}
update();
source = AudioSource.uri(Uri.parse(audio.url));
Expand Down Expand Up @@ -173,10 +175,10 @@ class AudioPlayerController extends PlayerController {
}

@override
void switchVideo(Video video) async {
void switchVideo(Video video, {Duration? startAt}) async {
offlineVideo = null;
this.video = video;
playVideo(false);
playVideo(false, startAt: startAt);
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/controllers/downloadController.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class DownloadController extends GetxController {
} else {
Video vid = await service.getVideo(videoId);
var downloadedVideo =
DownloadedVideo(videoId: vid.videoId, title: vid.title, author: vid.author, authorUrl: vid.authorUrl, audioOnly: audioOnly, videoLenthInSeconds: vid.lengthSeconds, quality: quality);
DownloadedVideo(videoId: vid.videoId, title: vid.title, author: vid.author, authorUrl: vid.authorUrl, audioOnly: audioOnly, lengthSeconds: vid.lengthSeconds, quality: quality);
db.upsertDownload(downloadedVideo);

String contentUrl;
Expand Down
2 changes: 1 addition & 1 deletion lib/controllers/interfaces/playerController.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ abstract class PlayerController extends GetxController {

void saveProgress(int timeInSeconds);

void switchVideo(Video video);
void switchVideo(Video video, {Duration? startAt});

void togglePlaying();

Expand Down
20 changes: 11 additions & 9 deletions lib/controllers/miniPayerController.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class MiniPlayerController extends GetxController {
List<String> playedVideos = [];
Offset offset = Offset.zero;
bool isAudio = true;
Duration? startAt;

List<DownloadedVideo> offlineVideos = [];

Expand Down Expand Up @@ -318,8 +319,9 @@ class MiniPlayerController extends GetxController {
}
}

_playVideos(List<IdedVideo> vids) async {
_playVideos(List<IdedVideo> vids, {Duration? startAt}) async {
if (vids.isNotEmpty) {
this.startAt = startAt;
bool isOffline = vids[0] is DownloadedVideo;

eventStream.add(MediaEvent(state: MediaState.loading));
Expand All @@ -346,12 +348,12 @@ class MiniPlayerController extends GetxController {
if (isOffline) {
await switchToOfflineVideo(offlineVideos[0]);
} else {
await switchToVideo(videos[0]);
await switchToVideo(videos[0], startAt: startAt);
}
}
}

_switchToVideo(IdedVideo video) async {
_switchToVideo(IdedVideo video, {Duration? startAt}) async {
bool isOffline = video is DownloadedVideo;

eventStream.add(MediaEvent(state: MediaState.loading));
Expand Down Expand Up @@ -381,7 +383,7 @@ class MiniPlayerController extends GetxController {
v = await service.getVideo(video.videoId);
}
currentlyPlaying = v;
playerController?.switchVideo(v);
playerController?.switchVideo(v, startAt: startAt);
} else {
offlineCurrentlyPlaying = video;
playerController?.switchToOfflineVideo(video);
Expand All @@ -407,23 +409,23 @@ class MiniPlayerController extends GetxController {
await _playVideos(offlineVids);
}

playVideo(List<BaseVideo> v, {bool? goBack, bool? audio}) async {
playVideo(List<BaseVideo> v, {bool? goBack, bool? audio, Duration? startAt}) async {
List<BaseVideo> videos = v.where((element) => !element.filtered).toList();
if (goBack ?? false) navigatorKey.currentState?.pop();
log.fine('Playing ${videos.length} videos');

setAudio(audio);

await _playVideos(videos);
await _playVideos(videos, startAt: startAt);
}

switchToOfflineVideo(DownloadedVideo video) async {
setAudio(video.audioOnly);
await _switchToVideo(video);
}

switchToVideo(BaseVideo video) async {
await _switchToVideo(video);
switchToVideo(BaseVideo video, {Duration? startAt}) async {
await _switchToVideo(video, startAt: startAt);
}

void togglePlaying() {
Expand Down Expand Up @@ -574,7 +576,7 @@ class MiniPlayerController extends GetxController {
} else if (offlineVideos.isNotEmpty) {
var e = offlineVideos[index];
var path = await e.thumbnailPath;
return MediaItem(id: e.videoId, title: e.title, artist: e.author, duration: Duration(seconds: e.videoLenthInSeconds), album: '', artUri: Uri.file(path));
return MediaItem(id: e.videoId, title: e.title, artist: e.author, duration: Duration(seconds: e.lengthSeconds), album: '', artUri: Uri.file(path));
}
return null;
}
Expand Down
3 changes: 1 addition & 2 deletions lib/controllers/miniplayerControlsController.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:get/get.dart';
import 'package:invidious/controllers/audioPlayerController.dart';
import 'package:invidious/controllers/miniPayerController.dart';
import 'package:invidious/controllers/videoPlayerController.dart';
import 'package:invidious/controllers/videoLikeController.dart';
import 'package:invidious/controllers/videoPlayerController.dart';

import '../utils.dart';
import 'interfaces/playerController.dart';
Expand All @@ -14,7 +14,6 @@ class MiniPlayerControlsController extends GetxController {

String videoId;


MiniPlayerController? get miniPlayerController => MiniPlayerController.to();

PlayerController? get player {
Expand Down
48 changes: 48 additions & 0 deletions lib/controllers/tvChannelController.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@ import 'package:invidious/globals.dart';

class TvChannelController extends ChannelController {
final ScrollController scrollController = ScrollController();
bool showBackground = false;
bool hasShorts = false, hasStreams = false, hasVideos = false, hasPlaylist = false;

TvChannelController(super.channelId);

@override
Future<void> onReady() async {
super.onReady();
scrollController.addListener(onScroll);
}

@override
void onClose() {
scrollController.dispose();
Expand All @@ -18,4 +26,44 @@ class TvChannelController extends ChannelController {
scrollController.animateTo(0, duration: animationDuration ~/ 2, curve: Curves.easeInOutQuad);
}
}

// we only update those values if is false because with continuation there could be no new values but we still want to display those
// meaning we expect it to be modified once.
setHasPlaylists(bool value) {
if (!hasPlaylist) {
hasPlaylist = value;
update();
}
}

setHasStreams(bool value) {
if (!hasStreams) {
hasStreams = value;
update();
}
}

setHasVideos(bool value) {
if (!hasVideos) {
hasVideos = value;
update();
}
}

setHasShorts(bool value) {
if (!hasShorts) {
hasShorts = value;
update();
}
}

void onScroll() {
if (scrollController.offset == 0) {
showBackground = false;
update();
} else if (!showBackground) {
showBackground = true;
update();
}
}
}
61 changes: 55 additions & 6 deletions lib/controllers/tvPlayerController.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import '../utils.dart';
const Duration controlFadeOut = Duration(seconds: 4);
const Duration throttleDuration = Duration(milliseconds: 250);

const defaultStep = 10;
const stepMultiplier = 0.2;

class TvPlayerController extends GetxController {
Logger log = Logger('TvPlayerController');

Expand All @@ -31,6 +34,7 @@ class TvPlayerController extends GetxController {
bool showQueue = false;
bool showControls = false;
late Video currentlyPlaying;
int forwardStep = defaultStep, rewindStep = defaultStep;

Duration get videoLength => Duration(seconds: VideoPlayerController.to()?.video?.lengthSeconds ?? 0);

Expand All @@ -39,15 +43,23 @@ class TvPlayerController extends GetxController {
togglePlayPause() {
showUi();
if (isPlaying) {
log.info('Pausing video');
VideoPlayerController.to()?.videoController?.pause();
pause();
} else {
log.info('Playing video');
VideoPlayerController.to()?.videoController?.play();
play();
}
update();
}

play() {
log.info('Playing video');
VideoPlayerController.to()?.videoController?.play();
}

pause() {
log.info('Pausing video');
VideoPlayerController.to()?.videoController?.pause();
}

@override
void onReady() async {
currentlyPlaying = await service.getVideo(videos[0].videoId);
Expand Down Expand Up @@ -114,11 +126,19 @@ class TvPlayerController extends GetxController {
}

fastForward() {
VideoPlayerController.to()?.videoController?.seekTo(currentPosition + const Duration(seconds: 10));
VideoPlayerController.to()?.videoController?.seekTo(currentPosition + Duration(seconds: forwardStep));
forwardStep += (forwardStep * stepMultiplier).floor();
EasyDebounce.debounce('fast-forward-step', const Duration(seconds: 1), () {
forwardStep = defaultStep;
});
}

fastRewind() {
VideoPlayerController.to()?.videoController?.seekTo(currentPosition - const Duration(seconds: 10));
VideoPlayerController.to()?.videoController?.seekTo(currentPosition - Duration(seconds: rewindStep));
rewindStep += (rewindStep * stepMultiplier).floor();
EasyDebounce.debounce('fast-rewind-step', const Duration(seconds: 1), () {
rewindStep = defaultStep;
});
}

playNext() async {
Expand Down Expand Up @@ -185,6 +205,35 @@ class TvPlayerController extends GetxController {
update();
return KeyEventResult.handled;
} else if (event is KeyUpEvent) {
switch (event.logicalKey) {
case LogicalKeyboardKey.mediaPlay:
play();
break;
case LogicalKeyboardKey.mediaPause:
pause();
break;
case LogicalKeyboardKey.mediaPlayPause:
togglePlayPause();
break;

case LogicalKeyboardKey.mediaFastForward:
case LogicalKeyboardKey.mediaStepForward:
case LogicalKeyboardKey.mediaSkipForward:
fastForward();
break;
case LogicalKeyboardKey.mediaRewind:
case LogicalKeyboardKey.mediaStepBackward:
case LogicalKeyboardKey.mediaSkipBackward:
fastRewind();
break;
case LogicalKeyboardKey.mediaTrackNext:
playNext();
break;
case LogicalKeyboardKey.mediaTrackPrevious:
playPrevious();
break;
}

if (timeLineControl) {
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
fastForward();
Expand Down
Loading