check id before play and fix timecode range if required
[omnplay] / src / omnplay.cpp
1 /*
2 * omnplay.c -- GTK+ 2 omnplay
3 * Copyright (C) 2011 Maksym Veremeyenko <verem@m1stereo.tv>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software Foundation,
17 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <pthread.h>
30
31 #include "omnplay.h"
32 #include "ui.h"
33 #include "opts.h"
34 #include "timecode.h"
35
36 #include "omplrclnt.h"
37
38 static gboolean on_main_window_delete_event( GtkWidget *widget, GdkEvent *event, gpointer user_data )
39 {
40 gtk_exit(0);
41 return TRUE;
42 }
43
44 omnplay_instance_t* omnplay_create(int argc, char** argv)
45 {
46 int i, c;
47 omnplay_instance_t* app;
48
49 /* prepare application instance */
50 app = (omnplay_instance_t*)malloc(sizeof(omnplay_instance_t));
51 memset(app, 0, sizeof(omnplay_instance_t));
52
53 /* load parameters from command line */
54 if(!omnplay_opt(argc, argv, app) && app->players.count)
55 app->window = ui_omnplay(app);
56 else
57 omnplay_usage();
58
59 return app;
60 };
61
62 void omnplay_destroy(omnplay_instance_t* app)
63 {
64 free(app);
65 };
66
67 static void omnplay_update_status(omnplay_player_t* player, OmPlrStatus *prev , OmPlrStatus *curr)
68 {
69 char tc_cur[32], tc_rem[32], state[32], status[32];
70 const char *clip;
71
72 if(curr)
73 {
74 frames2tc(curr->pos - curr->minPos, 25.0, tc_cur);
75 frames2tc(curr->maxPos - curr->pos, 25.0, tc_rem);
76 strcpy(status, "ONLINE");
77 clip = curr->currClipName;
78
79 switch(curr->state)
80 {
81 case omPlrStateStopped: strcpy(state, "STOPPED"); break;
82 case omPlrStateCuePlay: strcpy(state, "CUE_PLAY"); break;
83 case omPlrStatePlay: strcpy(state, "PLAY"); break;
84 case omPlrStateCueRecord: strcpy(state, "CUE_RECORD"); break;
85 case omPlrStateRecord: strcpy(state, "RECORD"); break;
86 };
87 }
88 else
89 {
90 tc_cur[0] = 0;
91 tc_rem[0] = 0;
92 clip = "";
93 state[0] = 0;
94 strcpy(status, "OFFLINE");
95 };
96
97 gdk_threads_enter();
98 gtk_label_set_text(GTK_LABEL (player->label_tc_cur), tc_cur);
99 gtk_label_set_text(GTK_LABEL (player->label_tc_rem), tc_rem);
100 gtk_label_set_text(GTK_LABEL (player->label_state), state);
101 gtk_label_set_text(GTK_LABEL (player->label_status), status);
102 gtk_label_set_text(GTK_LABEL (player->label_clip), clip);
103 gdk_flush();
104 gdk_threads_leave();
105
106 memcpy(prev, curr, sizeof(OmPlrStatus));
107 };
108
109 static void* omnplay_thread_proc(void* data)
110 {
111 int r;
112 OmPlrStatus st_curr, st_prev;
113 omnplay_player_t* player = (omnplay_player_t*)data;
114
115 /* connect */
116 pthread_mutex_lock(&player->app->players.lock);
117 r = OmPlrOpen(player->host, player->name, (OmPlrHandle*)&player->handle);
118 pthread_mutex_unlock(&player->app->players.lock);
119 if(r)
120 {
121 fprintf(stderr, "ERROR: OmPlrOpen(%s, %s) failed with 0x%.8X\n",
122 player->host, player->name, r);
123
124 return (void*)r;
125 };
126
127 /* setup to do not reconnect */
128 pthread_mutex_lock(&player->app->players.lock);
129 OmPlrSetRetryOpen((OmPlrHandle)player->handle, 0);
130 pthread_mutex_unlock(&player->app->players.lock);
131
132 /* setup directory */
133 if(player->app->players.path[0])
134 {
135 pthread_mutex_lock(&player->app->players.lock);
136 r = OmPlrClipSetDirectory((OmPlrHandle)player->handle, player->app->players.path);
137 pthread_mutex_unlock(&player->app->players.lock);
138
139 if(r)
140 {
141 fprintf(stderr, "ERROR: OmPlrClipSetDirectory(%s) failed with 0x%.8X\n",
142 player->app->players.path, r);
143
144 pthread_mutex_lock(&player->app->players.lock);
145 OmPlrClose((OmPlrHandle)player->handle);
146 pthread_mutex_unlock(&player->app->players.lock);
147
148 return (void*)r;
149 };
150 };
151
152 /* endless loop */
153 for(r = 0 ; !player->app->f_exit && !r;)
154 {
155 /* sleep */
156 usleep(100000);
157
158 /* get status */
159 pthread_mutex_lock(&player->app->players.lock);
160 st_curr.size = sizeof(OmPlrStatus);
161 r = OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &st_curr);
162 pthread_mutex_unlock(&player->app->players.lock);
163
164 if(r)
165 fprintf(stderr, "ERROR: OmPlrGetPlayerStatus failed with 0x%.8X\n", r);
166 else
167 if(memcmp(&st_curr, &st_prev, sizeof(OmPlrStatus)))
168 omnplay_update_status(player, &st_prev , &st_curr);
169 };
170
171 pthread_mutex_lock(&player->app->players.lock);
172 OmPlrClose((OmPlrHandle)player->handle);
173 pthread_mutex_unlock(&player->app->players.lock);
174
175 return NULL;
176 };
177
178 static int get_first_selected_item_playlist(omnplay_instance_t* app)
179 {
180 int idx;
181 GtkTreeIter iter;
182 GtkTreeModel *model;
183 GtkTreeSelection *selection;
184
185 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid));
186 if(selection && gtk_tree_selection_get_selected(selection, &model, &iter))
187 {
188 gtk_tree_model_get(model, &iter, 7, &idx, -1);
189 return idx;
190 };
191 return -1;
192 };
193
194 static int get_playlist_block(omnplay_instance_t* app, int idx, int* start_ptr, int* stop_ptr)
195 {
196 int start, stop;
197
198 for(start = idx; start >= 0; start--)
199 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN)
200 break;
201
202 for(stop = idx; stop < app->playlist.count; stop++)
203 if(app->playlist.item[stop].type & OMNPLAY_PLAYLIST_BLOCK_END)
204 break;
205
206 fprintf(stderr, "get_playlist_block: range %d -> %d\n", start, stop);
207
208 /* check block range */
209 if(start >= 0 && stop < app->playlist.count)
210 {
211 *start_ptr = start;
212 *stop_ptr = stop;
213 return (stop - start + 1);
214 };
215
216 return -1;
217 };
218
219 static omnplay_player_t *get_player_at_pos(omnplay_instance_t* app, int pos)
220 {
221 /* check player range */
222 if(app->playlist.item[pos].player > -1 && app->playlist.item[pos].player < app->players.count)
223 return &app->players.item[app->playlist.item[pos].player];
224
225 return NULL;
226 };
227
228 static void omnplay_ctl(omnplay_instance_t* app, control_buttons_t button)
229 {
230 int i, r;
231 int idx, start, stop;
232 omnplay_player_t *player;
233
234 pthread_mutex_lock(&app->playlist.lock);
235
236 idx = get_first_selected_item_playlist(app);
237
238 if(idx < 0)
239 {
240 pthread_mutex_unlock(&app->playlist.lock);
241 return;
242 };
243
244 fprintf(stderr, "cue: selected item is %d\n", idx);
245
246 if(get_playlist_block(app, idx, &start, &stop) < 0)
247 {
248 pthread_mutex_unlock(&app->playlist.lock);
249 return;
250 };
251
252 fprintf(stderr, "cue: range %d -> %d\n", start, stop);
253
254 player = get_player_at_pos(app, start);
255
256 if(!player)
257 {
258 pthread_mutex_unlock(&app->playlist.lock);
259 return;
260 };
261
262 pthread_mutex_lock(&app->players.lock);
263
264 if(BUTTON_PLAYER_STOP == button || BUTTON_PLAYER_CUE == button)
265 {
266 /* stop */
267 OmPlrStop((OmPlrHandle)player->handle);
268
269 /* detach previous clips */
270 player->playlist_start = -1;
271 player->playlist_count = -1;
272 OmPlrDetachAllClips((OmPlrHandle)player->handle);
273 };
274
275 if(BUTTON_PLAYER_CUE == button)
276 {
277 int o, c, p = 0;
278
279 /* Attach clips to timeline */
280 for(i = start, c = 0, o = 0; i <= stop; i++)
281 {
282 OmPlrClipInfo clip;
283
284 /* get clip info */
285 clip.maxMsTracks = 0;
286 clip.size = sizeof(clip);
287 r = OmPlrClipGetInfo((OmPlrHandle)player->handle, app->playlist.item[i].id, &clip);
288
289 if(!r)
290 {
291 unsigned int l;
292
293 fprintf(stderr, "OmPlrClipGetInfo(%s): firstFrame=%d, lastFrame=%d\n",
294 app->playlist.item[i].id, clip.firstFrame, clip.lastFrame);
295
296 /* should we fix playlist clip timings */
297 if((!
298 app->playlist.item[i].in >= clip.firstFrame &&
299 app->playlist.item[i].in + app->playlist.item[i].dur <= clip.lastFrame))
300 {
301 app->playlist.item[i].in = clip.firstFrame;
302 app->playlist.item[i].dur = clip.lastFrame - clip.firstFrame;
303 omnplay_playlist_draw_item(app, i);
304 };
305
306 r = OmPlrAttach((OmPlrHandle)player->handle,
307 app->playlist.item[i].id,
308 app->playlist.item[i].in,
309 app->playlist.item[i].in + app->playlist.item[i].dur,
310 0, omPlrShiftModeAfter, &l);
311 };
312
313 if(r)
314 {
315 fprintf(stderr, "cue: failed with %d, %s\n", r, OmPlrGetErrorString((OmPlrError)r));
316 app->playlist.item[i].omn_idx = -1;
317 app->playlist.item[i].omn_offset = -1;
318 }
319 else
320 {
321 app->playlist.item[i].omn_idx = c;
322 app->playlist.item[i].omn_offset = o;
323
324 /* save selected item offset */
325 if(i == idx) p = o;
326
327 c++;
328 o += app->playlist.item[i].dur;
329 };
330 };
331
332 if(c)
333 {
334 OmPlrStatus hs;
335
336 /* Set timeline min/max */
337 OmPlrSetMinPosMin((OmPlrHandle)player->handle);
338 OmPlrSetMaxPosMax((OmPlrHandle)player->handle);
339
340 /* Set timeline position */
341 hs.minPos = 0;
342 hs.size = sizeof(OmPlrStatus);
343 OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &hs);
344 OmPlrSetPos((OmPlrHandle)player->handle, hs.minPos + p);
345
346 /* Cue */
347 OmPlrCuePlay((OmPlrHandle)player->handle, 0.0);
348 OmPlrPlay((OmPlrHandle)player->handle, 0.0);
349
350 player->playlist_start = start;
351 player->playlist_count = stop - start + 1;
352 };
353 };
354
355 if(BUTTON_PLAYER_PLAY == button)
356 {
357 /* play */
358 OmPlrPlay((OmPlrHandle)player->handle, 1.0);
359 };
360
361 if(BUTTON_PLAYER_PAUSE == button)
362 /* pause */
363 OmPlrPlay((OmPlrHandle)player->handle, 0.0);
364
365 pthread_mutex_unlock(&app->players.lock);
366
367 pthread_mutex_unlock(&app->playlist.lock);
368 };
369
370 static gboolean omnplay_button_click(omnplay_instance_t* app, control_buttons_t button)
371 {
372 switch(button)
373 {
374 case BUTTON_PLAYLIST_ITEM_ADD:
375 case BUTTON_PLAYLIST_ITEM_DEL:
376 case BUTTON_PLAYLIST_ITEM_EDIT:
377 break;
378 case BUTTON_PLAYLIST_LOAD:
379 omnplay_playlist_load(app);
380 break;
381 case BUTTON_PLAYLIST_SAVE:
382 omnplay_playlist_save(app);
383 break;
384 case BUTTON_PLAYLIST_BLOCK_SINGLE:
385 case BUTTON_PLAYLIST_BLOCK_LOOP:
386 break;
387 case BUTTON_PLAYLIST_ITEM_UP:
388 case BUTTON_PLAYLIST_ITEM_DOWN:
389 break;
390 case BUTTON_PLAYER_CUE:
391 case BUTTON_PLAYER_PLAY:
392 case BUTTON_PLAYER_PAUSE:
393 case BUTTON_PLAYER_STOP:
394 omnplay_ctl(app, button);
395 break;
396 case BUTTON_LIBRARY_ADD:
397 case BUTTON_LIBRARY_REFRESH:
398 break;
399 };
400
401 return TRUE;
402 };
403
404 static gboolean on_button_click(GtkWidget *button, gpointer user_data)
405 {
406 int i;
407 omnplay_instance_t* app = (omnplay_instance_t*)user_data;
408
409 for(i = 1; i < BUTTON_LAST; i++)
410 if(app->buttons[i] == button)
411 return omnplay_button_click(app, (control_buttons_t)i);
412
413 return FALSE;
414 };
415
416 void omnplay_init(omnplay_instance_t* app)
417 {
418 int i;
419
420 gtk_signal_connect( GTK_OBJECT( app->window ), "destroy",
421 GTK_SIGNAL_FUNC(on_main_window_delete_event), app);
422
423 /* create lock */
424 pthread_mutex_init(&app->players.lock, NULL);
425
426 /* create a omneon status thread */
427 for(i = 0; i < app->players.count; i++)
428 pthread_create(&app->players.item[i].thread, NULL,
429 omnplay_thread_proc, &app->players.item[i]);
430
431 /* create lock */
432 pthread_mutex_init(&app->playlist.lock, NULL);
433
434 /* attach buttons click */
435 for(i = 1; i < BUTTON_LAST; i++)
436 gtk_signal_connect(GTK_OBJECT(app->buttons[i]), "clicked",
437 GTK_SIGNAL_FUNC( on_button_click), app );
438
439 };
440
441 void omnplay_release(omnplay_instance_t* app)
442 {
443 int i;
444 void* r;
445
446 app->f_exit = 1;
447
448 for(i = 0; i < app->players.count; i++)
449 /* create a omneon status thread */
450 pthread_join(app->players.item[i].thread, &r);
451
452 /* destroy lock */
453 pthread_mutex_destroy(&app->players.lock);
454
455 /* destroy lock */
456 pthread_mutex_destroy(&app->playlist.lock);
457 };