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:
parent
181d894df0
commit
79fef51f68
@ -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 */,
|
||||
|
36
damus/Views/Extensions/VisibilityTracker.swift
Normal file
36
damus/Views/Extensions/VisibilityTracker.swift
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// VisibilityTracker.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Daniel D’Aquino 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user