1 // Copyright 2023-present 650 Industries. All rights reserved.
2 
3 import ExpoModulesCore
4 
5 public final class VideoModule: Module {
definitionnull6   public func definition() -> ModuleDefinition {
7     Name("ExpoVideo")
8 
9     View(VideoView.self) {
10       Prop("player") { (view, player: VideoPlayer?) in
11         view.player = player?.pointer
12       }
13 
14       Prop("nativeControls") { (view, nativeControls: Bool?) in
15         view.playerViewController.showsPlaybackControls = nativeControls ?? true
16       }
17 
18       Prop("contentFit") { (view, contentFit: VideoContentFit?) in
19         view.playerViewController.videoGravity = contentFit?.toVideoGravity() ?? .resizeAspect
20       }
21 
22       Prop("contentPosition") { (view, contentPosition: CGVector?) in
23         let layer = view.playerViewController.view.layer
24 
25         layer.frame = CGRect(
26           x: contentPosition?.dx ?? 0,
27           y: contentPosition?.dy ?? 0,
28           width: layer.frame.width,
29           height: layer.frame.height
30         )
31       }
32 
33       Prop("allowsFullscreen") { (view, allowsFullscreen: Bool?) in
34         view.playerViewController.setValue(allowsFullscreen ?? true, forKey: "allowsEnteringFullScreen")
35       }
36 
37       Prop("showsTimecodes") { (view, showsTimecodes: Bool?) in
38         view.playerViewController.showsTimecodes = showsTimecodes ?? true
39       }
40 
41       Prop("requiresLinearPlayback") { (view, requiresLinearPlayback: Bool?) in
42         view.playerViewController.requiresLinearPlayback = requiresLinearPlayback ?? false
43       }
44 
45       AsyncFunction("enterFullscreen") { view in
46         view.enterFullscreen()
47       }
48 
49       AsyncFunction("exitFullscreen") { view in
50         view.exitFullscreen()
51       }
52     }
53 
54     Class(VideoPlayer.self) {
55       Constructor { (source: String?) -> VideoPlayer in
56         if let source, let url = URL(string: source) {
57           let item = AVPlayerItem(url: url)
58           return VideoPlayer(AVPlayer(playerItem: item))
59         }
60         return VideoPlayer(AVPlayer())
61       }
62 
63       Property("isPlaying") { (player: VideoPlayer) in
64         return player.pointer.timeControlStatus == .playing
65       }
66 
67       Property("isMuted") { (player: VideoPlayer) -> Bool in
68         return player.pointer.isMuted
69       }
70       .set { (player, isMuted: Bool) in
71         player.pointer.isMuted = isMuted
72       }
73 
74       Property("currentTime") { (player: VideoPlayer) -> Double in
75         return player.pointer.currentTime().seconds
76       }
77 
78       Function("play") { player in
79         player.pointer.play()
80       }
81 
82       Function("pause") { player in
83         player.pointer.pause()
84       }
85 
86       Function("replace") { (player, source: String) in
87         guard let url = URL(string: source) else {
88           player.pointer.replaceCurrentItem(with: nil)
89           return
90         }
91         let newPlayerItem = AVPlayerItem(url: url)
92 
93         player.pointer.replaceCurrentItem(with: newPlayerItem)
94         player.pointer.play()
95       }
96 
97       Function("seekBy") { (player, seconds: Double) in
98         let newTime = player.pointer.currentTime() + CMTime(seconds: seconds, preferredTimescale: .max)
99 
100         player.pointer.seek(to: newTime)
101       }
102 
103       Function("replay") { player in
104         player.pointer.seek(to: CMTime.zero)
105       }
106     }
107   }
108 }
109