1
0
mirror of git://jb55.com/damus synced 2024-09-28 16:00:43 +00:00

Improve Video visibility tracking and automatic play/pause

The DamusAVPlayerView (along with other classes) play or pause based on
their Y position relative to the user's viewport. This is ideal for a
vertical feed of notes.

However, this does not work well on a horizontal carousel, such as when
viewing videos on the full-screen carousel and swiping left/right.

This commit adds a new tracking method based on onAppear/onDisappear
triggers from a lazy stack (which only loads when it is visible), and
applied it to videos shown on a full screen carousel, so that videos
pause when we swipe away from the video.

Incidentally, this also fixes an issue I was seeing where a full screen
video would disappear as soon as I rotated the phone to landscape mode.

Testing
--------

Device: iPhone 13 Mini
iOS: 17.3.1
Damus: This version
Coverage:
1. Scroll down a feed full of videos and make sure videos still autoplay when passing through them. PASS
2. Check videos on the feed are muted by default. PASS
3. Check mute button on the video still works. PASS
4. Check clicking on the video brings it to a full-screen carousel view. PASS
5. Check that videos play unmuted by default on full-screen carousel view. PASS
6. Check that all playback controls work on the full-screen carousel view. PASS
7. Check that clicking outside the video shows/hides the carousel overlays. PASS
8. Check that a summary of the note shows up. PASS
9. Check that clicking on that note takes the user to the thread view. PASS
10. Check that changing phone orientation between portrait and landscape on both full-screen carousel AND full-screen video modes will work as expected. PASS
11. Check close button on full-screen carousel works. PASS
12. Check that swiping the video away exits full-screen carousel. PASS
13. Check that full-screen carousel works with images. PASS
14. Check that a carousel with multiple images/videos can be swiped left and right. PASS
15. Check that swiping away from a video on a full-screen carousel will pause it. PASS
16. Check that clicking on an unmuted video on the feed won't cause double-audio issues. PASS
17. Check that full-screen carousel view looks good on both dark and light modes. PASS

Closes: https://github.com/damus-io/damus/issues/1530
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Link: 20240318222048.14226-6-daniel@daquino.me
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
Daniel D’Aquino 2024-03-18 22:21:43 +00:00 committed by William Casarin
parent 181d894df0
commit 79fef51f68
4 changed files with 74 additions and 5 deletions

View File

@ -454,6 +454,7 @@
D7100C5A2B76FD5100C59298 /* LogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C592B76FD5100C59298 /* LogoView.swift */; };
D7100C5C2B77016700C59298 /* IAPProductStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5B2B77016700C59298 /* IAPProductStateView.swift */; };
D7100C5E2B7709ED00C59298 /* PurpleStoreKitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */; };
D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */; };
D71DC1EC2A9129C3006E207C /* PostViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71DC1EB2A9129C3006E207C /* PostViewTests.swift */; };
D72341192B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; };
D723411A2B6864F200E1E135 /* DamusPurpleEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */; };
@ -1373,6 +1374,7 @@
D7100C592B76FD5100C59298 /* LogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogoView.swift; sourceTree = "<group>"; };
D7100C5B2B77016700C59298 /* IAPProductStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IAPProductStateView.swift; sourceTree = "<group>"; };
D7100C5D2B7709ED00C59298 /* PurpleStoreKitManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurpleStoreKitManager.swift; sourceTree = "<group>"; };
D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisibilityTracker.swift; sourceTree = "<group>"; };
D71DC1EB2A9129C3006E207C /* PostViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewTests.swift; sourceTree = "<group>"; };
D72341182B6864F200E1E135 /* DamusPurpleEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleEnvironment.swift; sourceTree = "<group>"; };
D723C38D2AB8D83400065664 /* ContentFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentFilters.swift; sourceTree = "<group>"; };
@ -1978,6 +1980,7 @@
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
D71AC4CA2BA8E3320076268E /* Extensions */,
BA3759952ABCCF360018D73B /* Camera */,
F71694E82A66221E001F4053 /* Onboarding */,
4C190F232A547D1700027FD5 /* NostrScript */,
@ -2706,6 +2709,14 @@
path = Detail;
sourceTree = "<group>";
};
D71AC4CA2BA8E3320076268E /* Extensions */ = {
isa = PBXGroup;
children = (
D71AC4CB2BA8E3480076268E /* VisibilityTracker.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
D72A2D032AD9C165002AFF62 /* Mocking */ = {
isa = PBXGroup;
children = (
@ -3206,6 +3217,7 @@
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */,
4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */,
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
D71AC4CC2BA8E3480076268E /* VisibilityTracker.swift in Sources */,
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,

View File

@ -0,0 +1,36 @@
//
// VisibilityTracker.swift
// damus
//
// Created by Daniel DAquino on 2024-03-18.
//
// Based on code examples shown in this article: https://medium.com/@jackvanderpump/how-to-detect-is-an-element-is-visible-in-swiftui-9ff58ca72339
import Foundation
import SwiftUI
extension View {
func on_visibility_change(perform visibility_change_notifier: @escaping (Bool) -> Void, edge: Alignment = .center) -> some View {
self.modifier(VisibilityTracker(visibility_change_notifier: visibility_change_notifier, edge: edge))
}
}
struct VisibilityTracker: ViewModifier {
let visibility_change_notifier: (Bool) -> Void
let edge: Alignment
func body(content: Content) -> some View {
content
.overlay(
LazyVStack {
Color.clear
.onAppear {
visibility_change_notifier(true)
}
.onDisappear {
visibility_change_notifier(false)
}
},
alignment: edge)
}
}

View File

@ -46,7 +46,7 @@ struct ImageContainerView: View {
case .image(let url):
Img(url: url)
case .video(let url):
DamusVideoPlayer(url: url, video_size: .constant(nil), controller: video_controller, style: .full)
DamusVideoPlayer(url: url, video_size: .constant(nil), controller: video_controller, style: .full, visibility_tracking_method: .generic)
}
}
}

View File

@ -21,8 +21,10 @@ struct DamusVideoPlayer: View {
@StateObject var model: DamusVideoPlayerViewModel
@EnvironmentObject private var orientationTracker: OrientationTracker
let style: Style
let visibility_tracking_method: VisibilityTrackingMethod
@State var isVisible: Bool = false
init(url: URL, video_size: Binding<CGSize?>, controller: VideoController, style: Style) {
init(url: URL, video_size: Binding<CGSize?>, controller: VideoController, style: Style, visibility_tracking_method: VisibilityTrackingMethod = .y_scroll) {
self.url = url
let mute: Bool?
if case .full = style {
@ -32,6 +34,7 @@ struct DamusVideoPlayer: View {
mute = nil
}
_model = StateObject(wrappedValue: DamusVideoPlayerViewModel(url: url, video_size: video_size, controller: controller, mute: mute))
self.visibility_tracking_method = visibility_tracking_method
self.style = style
}
@ -67,14 +70,25 @@ struct DamusVideoPlayer: View {
}
}
.onChange(of: centerY) { _ in
update_is_visible(centerY: centerY)
if case .y_scroll = visibility_tracking_method {
update_is_visible(centerY: centerY)
}
}
.on_visibility_change(perform: { new_visibility in
if case .generic = visibility_tracking_method {
model.set_view_is_visible(new_visibility)
}
})
.onAppear {
update_is_visible(centerY: centerY)
if case .y_scroll = visibility_tracking_method {
update_is_visible(centerY: centerY)
}
}
}
.onDisappear {
model.view_did_disappear()
if case .y_scroll = visibility_tracking_method {
model.view_did_disappear()
}
}
}
@ -141,6 +155,13 @@ struct DamusVideoPlayer: View {
/// A style suitable for muted, auto-playing videos on a feed
case preview(on_tap: (() -> Void)?)
}
enum VisibilityTrackingMethod {
/// Detects visibility based on its Y position relative to viewport. Ideal for long feeds
case y_scroll
/// Detects visibility based whether the view intersects with the viewport
case generic
}
}
struct DamusVideoPlayer_Previews: PreviewProvider {
static var previews: some View {