damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit f1f3abfb98da7bd1f88defab3dc49199ab68e763
parent dec07df2c147735de2c8d1702d34b0203f263000
Author: Bryan Montz <bryanmontz@me.com>
Date:   Wed,  6 Sep 2023 11:25:07 -0500

video: add DamusVideoPlayerViewModel

Closes: https://github.com/damus-io/damus/pull/1539
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 4++++
Adamus/Views/Video/DamusVideoPlayerViewModel.swift | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 109 insertions(+), 0 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -389,6 +389,7 @@ 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 504323A82A3495B6006AE6DC /* RelayModelCache.swift */; }; 5053ACA72A56DF3B00851AE3 /* DeveloperSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */; }; 50A16FFB2AA6C06600DFEC1F /* AVPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFA2AA6C06600DFEC1F /* AVPlayerView.swift */; }; + 50A16FFD2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */; }; 50A16FFF2AA76A0900DFEC1F /* VideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A16FFE2AA76A0900DFEC1F /* VideoController.swift */; }; 50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; }; 50A60D142A28BEEE00186190 /* RelayLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A60D132A28BEEE00186190 /* RelayLog.swift */; }; @@ -1066,6 +1067,7 @@ 504323A82A3495B6006AE6DC /* RelayModelCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayModelCache.swift; sourceTree = "<group>"; }; 5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperSettingsView.swift; sourceTree = "<group>"; }; 50A16FFA2AA6C06600DFEC1F /* AVPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayerView.swift; sourceTree = "<group>"; }; + 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusVideoPlayerViewModel.swift; sourceTree = "<group>"; }; 50A16FFE2AA76A0900DFEC1F /* VideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoController.swift; sourceTree = "<group>"; }; 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; }; 50A60D132A28BEEE00186190 /* RelayLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayLog.swift; sourceTree = "<group>"; }; @@ -1362,6 +1364,7 @@ isa = PBXGroup; children = ( 4C1A9A2929DDF54400516EAC /* DamusVideoPlayer.swift */, + 50A16FFC2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift */, 50A16FFE2AA76A0900DFEC1F /* VideoController.swift */, 4CCF9AAE2A1FDBDB00E03CFB /* VideoPlayer.swift */, 50A16FFA2AA6C06600DFEC1F /* AVPlayerView.swift */, @@ -2733,6 +2736,7 @@ 4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */, 5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */, 4CB88396296F7F8B00DC99E7 /* ReactionView.swift in Sources */, + 50A16FFD2AA7525700DFEC1F /* DamusVideoPlayerViewModel.swift in Sources */, 4CFF8F6B29CD0079008DB934 /* RepostedEvent.swift in Sources */, 4C8682872814DE470026224F /* ProfileView.swift in Sources */, 5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */, diff --git a/damus/Views/Video/DamusVideoPlayerViewModel.swift b/damus/Views/Video/DamusVideoPlayerViewModel.swift @@ -0,0 +1,105 @@ +// +// DamusVideoPlayerViewModel.swift +// damus +// +// Created by Bryan Montz on 9/5/23. +// + +import AVFoundation +import Combine +import Foundation +import SwiftUI + +@MainActor +final class DamusVideoPlayerViewModel: ObservableObject { + + private let url: URL + private let player_item: AVPlayerItem + let player: AVPlayer + private let controller: VideoController + let id = UUID() + + @Published var has_audio = false + @Binding var video_size: CGSize? + @Published var is_muted = true + @Published var is_loading = true + + private var cancellables = Set<AnyCancellable>() + + private var is_scrolled_into_view = false { + didSet { + if is_scrolled_into_view && !oldValue { + // we have just scrolled from out of view into view + controller.focused_model_id = id + } else if !is_scrolled_into_view && oldValue { + // we have just scrolled from in view to out of view + if controller.focused_model_id == id { + controller.focused_model_id = nil + } + } + } + } + + init(url: URL, video_size: Binding<CGSize?>, controller: VideoController) { + self.url = url + player_item = AVPlayerItem(url: url) + player = AVPlayer(playerItem: player_item) + self.controller = controller + _video_size = video_size + + Task { + await load() + } + + is_muted = controller.should_mute_video(url: url) + player.isMuted = is_muted + + NotificationCenter.default.addObserver( + self, + selector: #selector(did_play_to_end), + name: Notification.Name.AVPlayerItemDidPlayToEndTime, + object: player_item + ) + + controller.$focused_model_id + .sink { [weak self] model_id in + model_id == self?.id ? self?.player.play() : self?.player.pause() + } + .store(in: &cancellables) + } + + private func load() async { + if let meta = controller.metadata(for: url) { + has_audio = meta.has_audio + video_size = meta.size + } else { + has_audio = await video_has_audio(player: player) + if let video_size = await get_video_size(player: player) { + self.video_size = video_size + let meta = VideoMetadata(has_audio: has_audio, size: video_size) + controller.set_metadata(meta, url: url) + } + } + + is_loading = false + } + + func did_tap_mute_button() { + is_muted.toggle() + player.isMuted = is_muted + controller.toggle_should_mute_video(url: url) + } + + func set_view_is_visible(_ is_visible: Bool) { + is_scrolled_into_view = is_visible + } + + func view_did_disappear() { + set_view_is_visible(false) + } + + @objc private func did_play_to_end() { + player.seek(to: CMTime.zero) + player.play() + } +}