@@ -5,11 +5,15 @@ use necromancer::{
55use std:: {
66 collections:: HashMap ,
77 net:: { Ipv4Addr , SocketAddrV4 } ,
8- sync:: Arc ,
8+ sync:: { Arc , OnceLock } ,
99} ;
1010use thiserror:: Error ;
1111use tokio:: sync:: watch;
12- use tokio:: { sync:: broadcast, task:: JoinHandle } ;
12+ use tokio:: {
13+ runtime:: { Builder , Runtime } ,
14+ sync:: broadcast,
15+ task:: JoinHandle ,
16+ } ;
1317use tracing:: { debug, info} ;
1418
1519#[ derive( Debug , Error ) ]
@@ -47,6 +51,37 @@ pub struct AtemSnapshot {
4751 ///
4852 /// This allows the UI to offer DSK controls only when supported.
4953 pub dsk_keys : Vec < u8 > ,
54+ /// DSK source assignments: key -> (fill, cut)
55+ pub dsk_sources : HashMap < u8 , ( VideoSource , VideoSource ) > ,
56+ /// DSK runtime state flags by key.
57+ pub dsk_state : HashMap < u8 , DskRuntimeSnapshot > ,
58+ /// DSK configuration properties by key.
59+ pub dsk_properties : HashMap < u8 , DskPropertiesSnapshot > ,
60+ /// Transition position percent-like value (0..=10000) by ME.
61+ pub transition_position : Vec < u16 > ,
62+ /// Whether transition is currently moving by ME.
63+ pub transition_in_progress : Vec < bool > ,
64+ /// Fade-to-black fully black status by ME.
65+ pub ftb_fully_black : Vec < bool > ,
66+ /// Fade-to-black in-transition status by ME.
67+ pub ftb_in_transition : Vec < bool > ,
68+ /// Fade-to-black frames remaining by ME.
69+ pub ftb_frames_remaining : Vec < u8 > ,
70+ /// Fade-to-black rate by ME.
71+ pub ftb_rate : Vec < u8 > ,
72+ }
73+
74+ #[ derive( Debug , Clone , Copy ) ]
75+ pub struct DskPropertiesSnapshot {
76+ pub tie : bool ,
77+ pub rate : u8 ,
78+ }
79+
80+ #[ derive( Debug , Clone , Copy ) ]
81+ pub struct DskRuntimeSnapshot {
82+ pub on_air : bool ,
83+ pub in_transition : bool ,
84+ pub remaining_frames : u8 ,
5085}
5186
5287impl AtemSnapshot {
@@ -65,6 +100,65 @@ impl AtemSnapshot {
65100
66101 let tally_by_source = state. tally_by_source . clone ( ) ;
67102 let dsk_keys = state. dsk_sources . keys ( ) . copied ( ) . collect :: < Vec < _ > > ( ) ;
103+ let dsk_sources = state. dsk_sources . clone ( ) ;
104+ let dsk_state = state
105+ . dsk_state
106+ . iter ( )
107+ . map ( |( k, v) | {
108+ (
109+ * k,
110+ DskRuntimeSnapshot {
111+ on_air : v. on_air ,
112+ in_transition : v. in_transition ,
113+ remaining_frames : v. remaining_frames ,
114+ } ,
115+ )
116+ } )
117+ . collect :: < HashMap < _ , _ > > ( ) ;
118+ let dsk_properties = state
119+ . dsk_properties
120+ . iter ( )
121+ . map ( |( k, v) | {
122+ (
123+ * k,
124+ DskPropertiesSnapshot {
125+ tie : v. tie ,
126+ rate : v. rate ,
127+ } ,
128+ )
129+ } )
130+ . collect :: < HashMap < _ , _ > > ( ) ;
131+
132+ let transition_position = ( 0 ..mes_count)
133+ . map ( |me| {
134+ state
135+ . transition_position
136+ . get ( & me)
137+ . map ( |p| p. position )
138+ . unwrap_or_default ( )
139+ } )
140+ . collect :: < Vec < _ > > ( ) ;
141+ let transition_in_progress = ( 0 ..mes_count)
142+ . map ( |me| {
143+ state
144+ . transition_position
145+ . get ( & me)
146+ . map ( |p| p. in_progress )
147+ . unwrap_or ( false )
148+ } )
149+ . collect :: < Vec < _ > > ( ) ;
150+
151+ let mut ftb_fully_black = Vec :: with_capacity ( mes_count as usize ) ;
152+ let mut ftb_in_transition = Vec :: with_capacity ( mes_count as usize ) ;
153+ let mut ftb_frames_remaining = Vec :: with_capacity ( mes_count as usize ) ;
154+ let mut ftb_rate = Vec :: with_capacity ( mes_count as usize ) ;
155+ for me in 0 ..mes_count {
156+ let status = state. get_fade_to_black_status ( me) . unwrap_or_default ( ) ;
157+ ftb_fully_black. push ( status. fully_black ) ;
158+ ftb_in_transition. push ( status. in_transition ) ;
159+ ftb_frames_remaining. push ( status. frames_remaining ) ;
160+ ftb_rate. push ( state. get_fade_to_black_rate ( me) . unwrap_or_default ( ) ) ;
161+ }
68162
69163 Self {
70164 initialisation_complete : state. initialisation_complete ,
@@ -75,6 +169,15 @@ impl AtemSnapshot {
75169 available_sources,
76170 tally_by_source,
77171 dsk_keys,
172+ dsk_sources,
173+ dsk_state,
174+ dsk_properties,
175+ transition_position,
176+ transition_in_progress,
177+ ftb_fully_black,
178+ ftb_in_transition,
179+ ftb_frames_remaining,
180+ ftb_rate,
78181 }
79182 }
80183}
@@ -99,106 +202,103 @@ impl AtemConnection {
99202}
100203
101204impl AtemClientHandle {
102- pub async fn set_program_input (
103- & self ,
104- me : u8 ,
105- video_source : VideoSource ,
106- ) -> Result < ( ) , ClientError > {
107- self . controller . set_program_input ( me, video_source) . await ?;
205+ pub fn set_program_input ( & self , me : u8 , video_source : VideoSource ) -> Result < ( ) , ClientError > {
206+ tokio_runtime ( ) . block_on ( self . controller . set_program_input ( me, video_source) ) ?;
108207 Ok ( ( ) )
109208 }
110209
111- pub async fn set_preview_input (
112- & self ,
113- me : u8 ,
114- video_source : VideoSource ,
115- ) -> Result < ( ) , ClientError > {
116- self . controller . set_preview_input ( me, video_source) . await ?;
210+ pub fn set_preview_input ( & self , me : u8 , video_source : VideoSource ) -> Result < ( ) , ClientError > {
211+ tokio_runtime ( ) . block_on ( self . controller . set_preview_input ( me, video_source) ) ?;
117212 Ok ( ( ) )
118213 }
119214
120- pub async fn cut ( & self , me : u8 ) -> Result < ( ) , ClientError > {
121- self . controller . cut ( me) . await ?;
215+ pub fn cut ( & self , me : u8 ) -> Result < ( ) , ClientError > {
216+ tokio_runtime ( ) . block_on ( self . controller . cut ( me) ) ?;
122217 Ok ( ( ) )
123218 }
124219
125- pub async fn auto ( & self , me : u8 ) -> Result < ( ) , ClientError > {
126- self . controller . auto ( me) . await ?;
220+ pub fn auto ( & self , me : u8 ) -> Result < ( ) , ClientError > {
221+ tokio_runtime ( ) . block_on ( self . controller . auto ( me) ) ?;
127222 Ok ( ( ) )
128223 }
129224
130- pub async fn set_next_transition (
225+ pub fn set_next_transition (
131226 & self ,
132227 me : u8 ,
133228 next_transition : TransitionType ,
134229 ) -> Result < ( ) , ClientError > {
135- self . controller
136- . set_next_transition ( me, next_transition)
137- . await ?;
230+ tokio_runtime ( ) . block_on ( self . controller . set_next_transition ( me, next_transition) ) ?;
138231 Ok ( ( ) )
139232 }
140233
141234 // ---- Phase 2 additions (AUX / DSK) ----
142- pub async fn set_aux_source (
235+ pub fn set_aux_source (
143236 & self ,
144237 aux_bus : u8 ,
145238 video_source : VideoSource ,
146239 ) -> Result < ( ) , ClientError > {
147- self . controller
148- . set_aux_source ( aux_bus, video_source)
149- . await ?;
240+ tokio_runtime ( ) . block_on ( self . controller . set_aux_source ( aux_bus, video_source) ) ?;
150241 Ok ( ( ) )
151242 }
152243
153- pub async fn dsk_auto ( & self , key : u8 ) -> Result < ( ) , ClientError > {
154- self . controller . dsk_auto ( key) . await ?;
244+ pub fn dsk_auto ( & self , key : u8 ) -> Result < ( ) , ClientError > {
245+ tokio_runtime ( ) . block_on ( self . controller . dsk_auto ( key) ) ?;
155246 Ok ( ( ) )
156247 }
157248
158- pub async fn set_dsk_on_air ( & self , key : u8 , on_air : bool ) -> Result < ( ) , ClientError > {
159- self . controller . set_dsk_on_air ( key, on_air) . await ?;
249+ pub fn set_dsk_on_air ( & self , key : u8 , on_air : bool ) -> Result < ( ) , ClientError > {
250+ tokio_runtime ( ) . block_on ( self . controller . set_dsk_on_air ( key, on_air) ) ?;
160251 Ok ( ( ) )
161252 }
162253
163- pub async fn set_dsk_tie ( & self , key : u8 , tie : bool ) -> Result < ( ) , ClientError > {
164- self . controller . set_dsk_tie ( key, tie) . await ?;
254+ pub fn set_dsk_tie ( & self , key : u8 , tie : bool ) -> Result < ( ) , ClientError > {
255+ tokio_runtime ( ) . block_on ( self . controller . set_dsk_tie ( key, tie) ) ?;
165256 Ok ( ( ) )
166257 }
167258
168- pub async fn set_dsk_cut_source (
169- & self ,
170- key : u8 ,
171- source : VideoSource ,
172- ) -> Result < ( ) , ClientError > {
173- self . controller . set_dsk_cut_source ( key, source) . await ?;
259+ pub fn set_dsk_cut_source ( & self , key : u8 , source : VideoSource ) -> Result < ( ) , ClientError > {
260+ tokio_runtime ( ) . block_on ( self . controller . set_dsk_cut_source ( key, source) ) ?;
174261 Ok ( ( ) )
175262 }
176263
177- pub async fn set_dsk_fill_source (
178- & self ,
179- key : u8 ,
180- source : VideoSource ,
181- ) -> Result < ( ) , ClientError > {
182- self . controller . set_dsk_fill_source ( key, source) . await ?;
264+ pub fn set_dsk_fill_source ( & self , key : u8 , source : VideoSource ) -> Result < ( ) , ClientError > {
265+ tokio_runtime ( ) . block_on ( self . controller . set_dsk_fill_source ( key, source) ) ?;
266+ Ok ( ( ) )
267+ }
268+
269+ pub fn set_dsk_rate ( & self , key : u8 , rate : u8 ) -> Result < ( ) , ClientError > {
270+ tokio_runtime ( ) . block_on ( self . controller . set_dsk_rate ( key, rate) ) ?;
271+ Ok ( ( ) )
272+ }
273+
274+ pub fn cut_black ( & self , me : u8 , black : bool ) -> Result < ( ) , ClientError > {
275+ tokio_runtime ( ) . block_on ( self . controller . cut_black ( me, black) ) ?;
183276 Ok ( ( ) )
184277 }
185278
186- pub async fn set_dsk_rate ( & self , key : u8 , rate : u8 ) -> Result < ( ) , ClientError > {
187- self . controller . set_dsk_rate ( key , rate ) . await ?;
279+ pub fn toggle_auto_black ( & self , me : u8 ) -> Result < ( ) , ClientError > {
280+ tokio_runtime ( ) . block_on ( self . controller . toggle_auto_black ( me ) ) ?;
188281 Ok ( ( ) )
189282 }
190283}
191284
192- pub async fn connect_udp (
193- ip : & str ,
194- port : u16 ,
195- reconnect : bool ,
196- ) -> Result < AtemConnection , ClientError > {
285+ pub fn connect_udp ( ip : & str , port : u16 , reconnect : bool ) -> Result < AtemConnection , ClientError > {
197286 let ip: Ipv4Addr = ip
198287 . parse ( )
199288 . map_err ( |_| ClientError :: InvalidIp ( ip. to_string ( ) ) ) ?;
200289 let addr = SocketAddrV4 :: new ( ip, port) ;
201- connect_socketaddr ( addr, reconnect) . await
290+ tokio_runtime ( ) . block_on ( connect_socketaddr ( addr, reconnect) )
291+ }
292+
293+ fn tokio_runtime ( ) -> & ' static Runtime {
294+ static TOKIO_RUNTIME : OnceLock < Runtime > = OnceLock :: new ( ) ;
295+ TOKIO_RUNTIME . get_or_init ( || {
296+ Builder :: new_multi_thread ( )
297+ . worker_threads ( 2 )
298+ . enable_all ( )
299+ . build ( )
300+ . expect ( "failed to create internal tokio runtime for atem-core" )
301+ } )
202302}
203303
204304async fn connect_socketaddr (
@@ -217,7 +317,7 @@ async fn connect_socketaddr(
217317
218318 let mut state_updates = controller. state_update_events ( ) ;
219319 let controller_bg = controller. clone ( ) ;
220- let bg_task = tokio :: spawn ( async move {
320+ let bg_task = tokio_runtime ( ) . spawn ( async move {
221321 loop {
222322 match state_updates. recv ( ) . await {
223323 Ok ( ( _txn, _update) ) => {
0 commit comments