Skip to content

Commit

Permalink
fix: Timeline View frame jump (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
bdlukaa authored Feb 1, 2024
2 parents ca6f1db + c9c940c commit 244539e
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 59 deletions.
13 changes: 8 additions & 5 deletions lib/widgets/events/event_player_desktop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class _EventPlayerDesktopState extends State<EventPlayerDesktop> {
playingSubscription.cancel();
bufferSubscription.cancel();
focusNode.dispose();
videoController.dispose();
if (widget.player == null) videoController.dispose();
super.dispose();
}

Expand All @@ -140,11 +140,14 @@ class _EventPlayerDesktopState extends State<EventPlayerDesktop> {
debugPrint(mediaUrl);

if (mediaUrl != videoController.dataSource) {
videoController
..setDataSource(mediaUrl)
..setVolume(volume)
..setSpeed(speed);
debugPrint(
'Setting data source from ${videoController.dataSource} to $mediaUrl',
);
videoController.setDataSource(mediaUrl);
}
videoController
..setVolume(volume)
..setSpeed(speed);
}

@override
Expand Down
20 changes: 16 additions & 4 deletions lib/widgets/events/event_player_mobile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ class _EventPlayerMobile extends StatefulWidget {
}

class __EventPlayerMobileState extends State<_EventPlayerMobile> {
late final videoController = widget.player ?? UnityVideoPlayer.create();
late final videoController = widget.player ??
UnityVideoPlayer.create(
enableCache: true,
// TODO(bdlukaa): determine quality based on settings
quality: UnityVideoQuality.p480,
);

@override
void initState() {
Expand All @@ -82,9 +87,16 @@ class __EventPlayerMobileState extends State<_EventPlayerMobile> {
: widget.event.mediaURL.toString();

debugPrint(mediaUrl);
videoController
..setDataSource(mediaUrl)
..setSpeed(1.0);
if (videoController.dataSource != mediaUrl) {
debugPrint(
'Setting data source from ${videoController.dataSource} to $mediaUrl',
);
videoController
..setDataSource(mediaUrl)
..setSpeed(1.0);
} else {
videoController.setSpeed(1.0);
}

super.didChangeDependencies();
}
Expand Down
121 changes: 86 additions & 35 deletions lib/widgets/events_timeline/desktop/timeline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ class TimelineTile {
enableCache: true,
title: device.fullName,
);
videoController.setMultipleDataSource(
events.map((event) => event.videoUrl),
autoPlay: false,
);
}

Event? currentEvent(DateTime currentDate) {
return events
.firstWhereOrNull((event) => event.isPlaying(currentDate))
?.event;
}
}

Expand Down Expand Up @@ -174,30 +184,30 @@ class Timeline extends ChangeNotifier {
void _eventCallback(TimelineTile tile, {bool notify = true}) {
if (tile.videoController.duration <= Duration.zero) return;

final index = tiles.indexOf(tile);

final bufferFactor = tile.videoController.currentBuffer.inMilliseconds /
tile.videoController.duration.inMilliseconds;

// This only applies if the video is not buffered
if (bufferFactor < 1.0) {
if (tile.videoController.currentBuffer <
tile.videoController.currentPos) {
debugPrint('should pause for buffering');
pausedToBuffer.add(index);
stop();
} else if (pausedToBuffer.contains(index)) {
debugPrint('should play from buffering');
pausedToBuffer.remove(index);

if (pausedToBuffer.isEmpty) play();
}
} else if (pausedToBuffer.contains(index)) {
debugPrint('should play from buffering');
pausedToBuffer.remove(index);

if (pausedToBuffer.isEmpty) play();
}
// final index = tiles.indexOf(tile);

// final bufferFactor = tile.videoController.currentBuffer.inMilliseconds /
// tile.videoController.duration.inMilliseconds;

// // This only applies if the video is not buffered
// if (bufferFactor < 1.0) {
// if (tile.videoController.currentBuffer <
// tile.videoController.currentPos) {
// debugPrint('should pause $index for buffering');
// pausedToBuffer.add(index);
// stop();
// } else if (pausedToBuffer.contains(index)) {
// debugPrint('should play $index from buffering');
// pausedToBuffer.remove(index);

// if (pausedToBuffer.isEmpty) play();
// }
// } else if (pausedToBuffer.contains(index)) {
// debugPrint('should play $index from buffering');
// pausedToBuffer.remove(index);

// if (pausedToBuffer.isEmpty) play();
// }
if (notify) notifyListeners();
}

Expand Down Expand Up @@ -274,9 +284,10 @@ class Timeline extends ChangeNotifier {
currentPosition = position;
notifyListeners();

forEachEvent((tile, event) {
forEachEvent((tile, event) async {
if (!event.isPlaying(currentDate)) return;
tile.videoController.setDataSource(event.videoUrl);
final eventIndex = tile.events.indexOf(event);
await tile.videoController.jumpToIndex(eventIndex);

final position = event.position(currentDate);
tile.videoController.seekTo(position);
Expand Down Expand Up @@ -320,6 +331,8 @@ class Timeline extends ChangeNotifier {
play();
}

Duration get period => Duration(milliseconds: 1000 ~/ _speed);

final indicatorKey = GlobalKey(debugLabel: 'Indicator key');
final zoomController = ScrollController(
debugLabel: 'Zoom Indicator Controller',
Expand Down Expand Up @@ -377,30 +390,60 @@ class Timeline extends ChangeNotifier {
timer?.cancel();
timer = null;

forEachEvent((tile, event) {
tile.videoController.pause();

if (event.isPlaying(currentDate)) {
tile.videoController.seekTo(event.position(currentDate));
}
});

for (final tile in tiles) {
tile.videoController.pause();
}
notifyListeners();
}

void play([TimelineEvent? event]) {
debugPrint('Playing timeline with $period');
timer?.cancel();
timer ??= Timer.periodic(Duration(milliseconds: 1000 ~/ _speed), (timer) {
timer ??= Timer.periodic(period, (timer) {
if (event == null) {
currentPosition += const Duration(seconds: 1);
currentPosition += period;
notifyListeners();
}

forEachEvent((tile, e) {
if (tile.videoController.isPlaying) return;
if (!tile.events.any((e) => e.isPlaying(currentDate))) {
tile.videoController.pause();
return;
}
if (!e.isPlaying(currentDate)) return;

final position = e.position(currentDate);
if (tile.videoController.isPlaying) {
// if the video is late by a lot of seconds, seek to the current position
final isLate = position - tile.videoController.currentPos >
const Duration(milliseconds: 3000);
if (isLate) {
tile.videoController.seekTo(position);
debugPrint('Device is late. Seeking ${tile.device} to $position');
}
return;
}

if ((event != null && event == e) || e.isPlaying(currentDate)) {
if (event == null) {
tile.videoController.seekTo(e.position(currentDate));
tile.videoController.seekTo(position);
debugPrint('-- Seeking ${tile.device} to $position');
}
if (!tile.videoController.isPlaying) {
tile.videoController.start();
debugPrint('Playing ${tile.device}');
}
if (!tile.videoController.isPlaying) tile.videoController.start();
} else {
tile.videoController.pause();
debugPrint('Pausing ${tile.device}');
}
});
});
Expand Down Expand Up @@ -474,7 +517,6 @@ class _TimelineEventsViewState extends State<TimelineEventsView> {

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final loc = AppLocalizations.of(context);
final settings = context.watch<SettingsProvider>();
final home = context.watch<HomeProvider>();
Expand Down Expand Up @@ -633,7 +675,7 @@ class _TimelineEventsViewState extends State<TimelineEventsView> {
),
ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: _kTimelineTileHeight * 4.5,
maxHeight: _kTimelineTileHeight * 5.0,
),
child: LayoutBuilder(builder: (context, constraints) {
final tileWidth =
Expand Down Expand Up @@ -768,13 +810,15 @@ class _TimelineEventsViewState extends State<TimelineEventsView> {
child: Container(
width: 8,
height: 4,
color: theme.colorScheme.onBackground,
// color: theme.colorScheme.onBackground,
color: Colors.black,
),
),
Expanded(
child: Container(
color: theme.colorScheme.onBackground,
// color: theme.colorScheme.onBackground,
width: 1.8,
color: Colors.black,
),
),
]),
Expand Down Expand Up @@ -941,6 +985,13 @@ class _TimelineTile extends StatelessWidget {
// 1)]
// : theme.colorScheme.primary,
color: theme.colorScheme.primary,
// color: theme.extension<UnityColors>()!.successColor,
child: kDebugMode
? Text(
'${tile.events.indexOf(event)}',
style: const TextStyle(color: Colors.black),
)
: null,
),
),
]);
Expand Down
40 changes: 29 additions & 11 deletions lib/widgets/events_timeline/desktop/timeline_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -133,31 +133,41 @@ class _TimelineCardState extends State<TimelineCard> {
const TextSpan(text: '\n'),
if (states.isHovering)
TextSpan(
text: currentEvent
.position(widget.timeline.currentDate)
.humanReadableCompact(context),
text: kDebugMode
? currentEvent
.position(widget.timeline.currentDate)
.toString()
: currentEvent
.position(widget.timeline.currentDate)
.humanReadableCompact(context),
),
if (kDebugMode) ...[
const TextSpan(text: '\ndebug: '),
TextSpan(text: controller.currentPos.toString()),
TextSpan(
text:
controller.currentPos.humanReadableCompact(context),
'\ndiff: ${currentEvent.position(widget.timeline.currentDate) - controller.currentPos}',
),
TextSpan(
text: '\nindex: ${events.indexOf(currentEvent)}',
),
],
],
),
),
if (kDebugMode)
Align(
alignment: AlignmentDirectional.topEnd,
Positioned(
top: 36.0,
right: 0.0,
child: Text(
'debug buffering: '
'buffer: '
'${(widget.tile.videoController.currentBuffer.inMilliseconds / widget.tile.videoController.duration.inMilliseconds).toStringAsPrecision(2)}'
'\n${widget.tile.videoController.currentBuffer.humanReadableCompact(context)}',
style: theme.textTheme.labelLarge!.copyWith(
color: Colors.white,
shadows: outlinedText(strokeWidth: 0.75),
),
textAlign: TextAlign.end,
),
),
PositionedDirectional(
Expand All @@ -168,7 +178,7 @@ class _TimelineCardState extends State<TimelineCard> {
child: () {
if (controller.isBuffering) {
return const CircularProgressIndicator.adaptive(
strokeWidth: 2.0,
strokeWidth: 1.5,
);
}
if (isDownloaded || isDownloading || states.isHovering) {
Expand Down Expand Up @@ -219,11 +229,19 @@ class _TimelineCardState extends State<TimelineCard> {
),
SquaredIconButton(
tooltip: loc.showFullscreenCamera,
onPressed: () {
Navigator.of(context).pushNamed(
onPressed: () async {
final isPlaying = widget.timeline.isPlaying;
if (isPlaying) widget.timeline.stop();

await Navigator.of(context).pushNamed(
'/events',
arguments: {'event': currentEvent.event},
arguments: {
'event': currentEvent.event,
'videoPlayer': widget.tile.videoController,
},
);

if (isPlaying) widget.timeline.play();
},
icon: Icon(
Icons.fullscreen,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,20 @@ class UnityVideoPlayerFlutter extends UnityVideoPlayer {
}

@override
Future<void> setMultipleDataSource(List<String> url, {bool autoPlay = true}) {
Future<void> setMultipleDataSource(Iterable<String> url,
{bool autoPlay = true}) {
throw UnsupportedError(
'setMultipleDataSource is not supported on this platform',
);
}

@override
Future<void> jumpToIndex(int index) {
throw UnsupportedError(
'jumpToIndex is not supported on this platform',
);
}

// Volume in media kit goes from 0 to 100
@override
Future<void> setVolume(double volume) async =>
Expand Down
Loading

0 comments on commit 244539e

Please sign in to comment.