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:
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()
+ }
+}