block composing skeleton added
[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 #ifndef _GNU_SOURCE
21 #define _GNU_SOURCE
22 #endif
23
24 #ifdef HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <gtk/gtk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <pthread.h>
34
35 #include "omnplay.h"
36 #include "ui.h"
37 #include "opts.h"
38 #include "timecode.h"
39
40 #include "omplrclnt.h"
41
42 static gboolean on_main_window_delete_event( GtkWidget *widget, GdkEvent *event, gpointer user_data )
43 {
44 gtk_exit(0);
45 return TRUE;
46 }
47
48 omnplay_instance_t* omnplay_create(int argc, char** argv)
49 {
50 int i, c;
51 omnplay_instance_t* app;
52
53 /* prepare application instance */
54 app = (omnplay_instance_t*)malloc(sizeof(omnplay_instance_t));
55 memset(app, 0, sizeof(omnplay_instance_t));
56
57 /* load parameters from command line */
58 if(!omnplay_opt(argc, argv, app) && app->players.count)
59 app->window = ui_omnplay(app);
60 else
61 omnplay_usage();
62
63 return app;
64 };
65
66 void omnplay_destroy(omnplay_instance_t* app)
67 {
68 free(app);
69 };
70
71 static int find_index_of_playlist_item(omnplay_instance_t* app, int start, int idx)
72 {
73 while(1)
74 {
75 if(app->playlist.item[start].omn_idx == idx)
76 return start;
77
78 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_END)
79 break;
80
81 start++;
82 };
83
84 return -1;
85 };
86
87 static void omnplay_update_status(omnplay_player_t* player, OmPlrStatus *prev , OmPlrStatus *curr)
88 {
89 int idx;
90 char tc_cur[32], tc_rem[32], state[32], status[32];
91 const char *clip;
92
93 if(curr)
94 {
95 frames2tc(curr->pos - curr->minPos, 25.0, tc_cur);
96 frames2tc(curr->maxPos - curr->pos, 25.0, tc_rem);
97 strcpy(status, "ONLINE");
98 clip = curr->currClipName;
99
100 switch(curr->state)
101 {
102 case omPlrStateStopped: strcpy(state, "STOPPED"); break;
103 case omPlrStateCuePlay: strcpy(state, "CUE_PLAY"); break;
104 case omPlrStatePlay: strcpy(state, "PLAY"); break;
105 case omPlrStateCueRecord: strcpy(state, "CUE_RECORD"); break;
106 case omPlrStateRecord: strcpy(state, "RECORD"); break;
107 };
108 }
109 else
110 {
111 tc_cur[0] = 0;
112 tc_rem[0] = 0;
113 clip = "";
114 state[0] = 0;
115 strcpy(status, "OFFLINE");
116 };
117
118 /* update status in status page */
119 gdk_threads_enter();
120 gtk_label_set_text(GTK_LABEL (player->label_tc_cur), tc_cur);
121 gtk_label_set_text(GTK_LABEL (player->label_tc_rem), tc_rem);
122 gtk_label_set_text(GTK_LABEL (player->label_state), state);
123 gtk_label_set_text(GTK_LABEL (player->label_status), status);
124 gtk_label_set_text(GTK_LABEL (player->label_clip), clip);
125 gdk_flush();
126 gdk_threads_leave();
127
128 /* update remaining time */
129 gdk_threads_enter();
130 pthread_mutex_lock(&player->app->playlist.lock);
131 pthread_mutex_lock(&player->app->players.lock);
132 if(curr->state == omPlrStatePlay || curr->state == omPlrStateCuePlay)
133 {
134 idx = find_index_of_playlist_item(player->app, player->playlist_start, curr->currClipNum);
135 if(idx >= 0)
136 {
137 frames2tc(curr->currClipStartPos + curr->currClipLen - curr->pos, 25.0, tc_rem);
138 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
139 }
140 if(curr->currClipNum != prev->currClipNum && 1 != prev->numClips)
141 {
142 tc_rem[0] = 0;
143 idx = find_index_of_playlist_item(player->app, player->playlist_start, prev->currClipNum);
144 if(idx >= 0)
145 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
146 };
147 }
148 else
149 {
150 tc_rem[0] = 0;
151 idx = find_index_of_playlist_item(player->app, player->playlist_start, curr->currClipNum);
152 if(idx >= 0)
153 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
154 idx = find_index_of_playlist_item(player->app, player->playlist_start, prev->currClipNum);
155 if(idx >= 0)
156 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
157 };
158 pthread_mutex_unlock(&player->app->players.lock);
159 pthread_mutex_unlock(&player->app->playlist.lock);
160 gdk_flush();
161 gdk_threads_leave();
162
163
164 memcpy(prev, curr, sizeof(OmPlrStatus));
165 };
166
167 static void* omnplay_thread_proc(void* data)
168 {
169 int r;
170 OmPlrStatus st_curr, st_prev;
171 omnplay_player_t* player = (omnplay_player_t*)data;
172
173 /* connect */
174 pthread_mutex_lock(&player->app->players.lock);
175 r = OmPlrOpen(player->host, player->name, (OmPlrHandle*)&player->handle);
176 pthread_mutex_unlock(&player->app->players.lock);
177 if(r)
178 {
179 fprintf(stderr, "ERROR: OmPlrOpen(%s, %s) failed with 0x%.8X\n",
180 player->host, player->name, r);
181
182 return (void*)r;
183 };
184
185 /* setup to do not reconnect */
186 pthread_mutex_lock(&player->app->players.lock);
187 OmPlrSetRetryOpen((OmPlrHandle)player->handle, 0);
188 pthread_mutex_unlock(&player->app->players.lock);
189
190 /* setup directory */
191 if(player->app->players.path[0])
192 {
193 pthread_mutex_lock(&player->app->players.lock);
194 r = OmPlrClipSetDirectory((OmPlrHandle)player->handle, player->app->players.path);
195 pthread_mutex_unlock(&player->app->players.lock);
196
197 if(r)
198 {
199 fprintf(stderr, "ERROR: OmPlrClipSetDirectory(%s) failed with 0x%.8X\n",
200 player->app->players.path, r);
201
202 pthread_mutex_lock(&player->app->players.lock);
203 OmPlrClose((OmPlrHandle)player->handle);
204 pthread_mutex_unlock(&player->app->players.lock);
205
206 return (void*)r;
207 };
208 };
209
210 /* endless loop */
211 for(r = 0 ; !player->app->f_exit && !r;)
212 {
213 /* sleep */
214 usleep(100000);
215
216 /* get status */
217 pthread_mutex_lock(&player->app->players.lock);
218 st_curr.size = sizeof(OmPlrStatus);
219 r = OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &st_curr);
220 pthread_mutex_unlock(&player->app->players.lock);
221
222 if(r)
223 fprintf(stderr, "ERROR: OmPlrGetPlayerStatus failed with 0x%.8X\n", r);
224 else
225 if(memcmp(&st_curr, &st_prev, sizeof(OmPlrStatus)))
226 omnplay_update_status(player, &st_prev , &st_curr);
227 };
228
229 pthread_mutex_lock(&player->app->players.lock);
230 OmPlrClose((OmPlrHandle)player->handle);
231 pthread_mutex_unlock(&player->app->players.lock);
232
233 return NULL;
234 };
235
236 void get_selected_items_playlist_proc(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
237 {
238 int idx, *list = (int*)data;
239 gtk_tree_model_get(model, iter, 7, &idx, -1);
240 list[list[0] + 1] = idx;
241 list[0] = list[0] + 1;
242 };
243
244 static int* get_selected_items_playlist(omnplay_instance_t* app)
245 {
246 int* list = NULL;
247 GtkTreeSelection *selection;
248
249 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid));
250 if(selection)
251 {
252 list = (int*)malloc(sizeof(int) * (MAX_PLAYLIST_ITEMS + 1));
253 memset(list, 0, sizeof(int) * (MAX_PLAYLIST_ITEMS + 1));
254
255 gtk_tree_selection_selected_foreach(
256 selection,
257 get_selected_items_playlist_proc,
258 list);
259
260 if(!list[0])
261 {
262 free(list);
263 list = NULL;
264 };
265 };
266
267 return list;
268 };
269
270 static void omnplay_playlist_block(omnplay_instance_t* app, control_buttons_t button)
271 {
272 int start, stop;
273 int* list = get_selected_items_playlist(app);
274
275 if(!list)
276 return;
277
278 pthread_mutex_lock(&app->playlist.lock);
279 pthread_mutex_lock(&app->players.lock);
280
281 start = list[1];
282 stop = list[list[0]];
283
284 fprintf(stderr, "omnplay_playlist_block: [%d %d]\n",
285 start, stop);
286
287 pthread_mutex_unlock(&app->players.lock);
288 pthread_mutex_unlock(&app->playlist.lock);
289
290 free(list);
291 };
292
293 static int get_first_selected_item_playlist(omnplay_instance_t* app)
294 {
295 int idx;
296 int* list = get_selected_items_playlist(app);
297 if(!list) return -1;
298 idx = list[1];
299 free(list);
300 return idx;
301 };
302
303 static int get_playlist_block(omnplay_instance_t* app, int idx, int* start_ptr, int* stop_ptr)
304 {
305 int start, stop;
306
307 for(start = idx; start >= 0; start--)
308 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN)
309 break;
310
311 for(stop = idx; stop < app->playlist.count; stop++)
312 if(app->playlist.item[stop].type & OMNPLAY_PLAYLIST_BLOCK_END)
313 break;
314
315 fprintf(stderr, "get_playlist_block: range %d -> %d\n", start, stop);
316
317 /* check block range */
318 if(start >= 0 && stop < app->playlist.count)
319 {
320 *start_ptr = start;
321 *stop_ptr = stop;
322 return (stop - start + 1);
323 };
324
325 return -1;
326 };
327
328 static omnplay_player_t *get_player_at_pos(omnplay_instance_t* app, int pos)
329 {
330 /* check player range */
331 if(app->playlist.item[pos].player > -1 && app->playlist.item[pos].player < app->players.count)
332 return &app->players.item[app->playlist.item[pos].player];
333
334 return NULL;
335 };
336
337 static void omnplay_ctl(omnplay_instance_t* app, control_buttons_t button)
338 {
339 int i, r;
340 int idx, start, stop;
341 omnplay_player_t *player;
342
343 pthread_mutex_lock(&app->playlist.lock);
344
345 idx = get_first_selected_item_playlist(app);
346
347 if(idx < 0)
348 {
349 pthread_mutex_unlock(&app->playlist.lock);
350 return;
351 };
352
353 fprintf(stderr, "cue: selected item is %d\n", idx);
354
355 if(get_playlist_block(app, idx, &start, &stop) < 0)
356 {
357 pthread_mutex_unlock(&app->playlist.lock);
358 return;
359 };
360
361 fprintf(stderr, "cue: range %d -> %d\n", start, stop);
362
363 player = get_player_at_pos(app, start);
364
365 if(!player)
366 {
367 pthread_mutex_unlock(&app->playlist.lock);
368 return;
369 };
370
371 pthread_mutex_lock(&app->players.lock);
372
373 if(BUTTON_PLAYER_STOP == button || BUTTON_PLAYER_CUE == button)
374 {
375 /* stop */
376 OmPlrStop((OmPlrHandle)player->handle);
377
378 /* detach previous clips */
379 player->playlist_length = -1;
380 OmPlrDetachAllClips((OmPlrHandle)player->handle);
381 };
382
383 if(BUTTON_PLAYER_CUE == button)
384 {
385 int o, c, p = 0;
386
387 /* Attach clips to timeline */
388 for(i = start, c = 0, o = 0; i <= stop; i++)
389 {
390 OmPlrClipInfo clip;
391
392 /* get clip info */
393 clip.maxMsTracks = 0;
394 clip.size = sizeof(clip);
395 r = OmPlrClipGetInfo((OmPlrHandle)player->handle, app->playlist.item[i].id, &clip);
396
397 if(!r)
398 {
399 unsigned int l;
400
401 fprintf(stderr, "OmPlrClipGetInfo(%s): firstFrame=%d, lastFrame=%d\n",
402 app->playlist.item[i].id, clip.firstFrame, clip.lastFrame);
403
404 /* should we fix playlist clip timings */
405 if(!(
406 app->playlist.item[i].in >= clip.firstFrame &&
407 app->playlist.item[i].in + app->playlist.item[i].dur <= clip.lastFrame) ||
408 !app->playlist.item[i].dur)
409 {
410 fprintf(stderr, "cue: item [%s] will be updated [%d;%d]->[%d;%d]\n",
411 app->playlist.item[i].id,
412 app->playlist.item[i].in, app->playlist.item[i].dur,
413 clip.firstFrame, clip.lastFrame - clip.firstFrame);
414
415 app->playlist.item[i].in = clip.firstFrame;
416 app->playlist.item[i].dur = clip.lastFrame - clip.firstFrame;
417 omnplay_playlist_draw_item(app, i);
418 };
419
420 r = OmPlrAttach((OmPlrHandle)player->handle,
421 app->playlist.item[i].id,
422 app->playlist.item[i].in,
423 app->playlist.item[i].in + app->playlist.item[i].dur,
424 0, omPlrShiftModeAfter, &l);
425 };
426
427 if(r)
428 {
429 fprintf(stderr, "cue: failed with %d, %s\n", r, OmPlrGetErrorString((OmPlrError)r));
430 app->playlist.item[i].omn_idx = -1;
431 app->playlist.item[i].omn_offset = -1;
432 }
433 else
434 {
435 app->playlist.item[i].omn_idx = c;
436 app->playlist.item[i].omn_offset = o;
437
438 /* save selected item offset */
439 if(i == idx) p = o;
440
441 c++;
442 o += app->playlist.item[i].dur;
443 };
444 };
445
446 if(c)
447 {
448 OmPlrStatus hs;
449
450 /* Set timeline min/max */
451 OmPlrSetMinPosMin((OmPlrHandle)player->handle);
452 OmPlrSetMaxPosMax((OmPlrHandle)player->handle);
453
454 /* Set timeline position */
455 hs.minPos = 0;
456 hs.size = sizeof(OmPlrStatus);
457 OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &hs);
458 OmPlrSetPos((OmPlrHandle)player->handle, hs.minPos + p);
459
460 /* setup loop */
461 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_LOOP)
462 OmPlrLoop((OmPlrHandle)player->handle, hs.minPos, hs.maxPos);
463
464 player->playlist_start = start;
465 player->playlist_length = stop - start + 1;
466
467 /* Cue */
468 OmPlrCuePlay((OmPlrHandle)player->handle, 0.0);
469 };
470 };
471
472 if(BUTTON_PLAYER_PLAY == button)
473 {
474 /* play */
475 OmPlrPlay((OmPlrHandle)player->handle, 1.0);
476 };
477
478 if(BUTTON_PLAYER_PAUSE == button)
479 /* pause */
480 OmPlrPlay((OmPlrHandle)player->handle, 0.0);
481
482 pthread_mutex_unlock(&app->players.lock);
483
484 pthread_mutex_unlock(&app->playlist.lock);
485 };
486
487 static gboolean omnplay_button_click(omnplay_instance_t* app, control_buttons_t button)
488 {
489 switch(button)
490 {
491 case BUTTON_PLAYLIST_ITEM_ADD:
492 case BUTTON_PLAYLIST_ITEM_DEL:
493 case BUTTON_PLAYLIST_ITEM_EDIT:
494 break;
495 case BUTTON_PLAYLIST_LOAD:
496 omnplay_playlist_load(app);
497 break;
498 case BUTTON_PLAYLIST_SAVE:
499 omnplay_playlist_save(app);
500 break;
501 case BUTTON_PLAYLIST_BLOCK_SINGLE:
502 case BUTTON_PLAYLIST_BLOCK_LOOP:
503 omnplay_playlist_block(app, button);
504 break;
505 case BUTTON_PLAYLIST_ITEM_UP:
506 case BUTTON_PLAYLIST_ITEM_DOWN:
507 break;
508 case BUTTON_PLAYER_CUE:
509 case BUTTON_PLAYER_PLAY:
510 case BUTTON_PLAYER_PAUSE:
511 case BUTTON_PLAYER_STOP:
512 omnplay_ctl(app, button);
513 break;
514 case BUTTON_LIBRARY_ADD:
515 case BUTTON_LIBRARY_REFRESH:
516 break;
517 };
518
519 return TRUE;
520 };
521
522 static gboolean on_button_click(GtkWidget *button, gpointer user_data)
523 {
524 int i;
525 omnplay_instance_t* app = (omnplay_instance_t*)user_data;
526
527 for(i = 1; i < BUTTON_LAST; i++)
528 if(app->buttons[i] == button)
529 return omnplay_button_click(app, (control_buttons_t)i);
530
531 return FALSE;
532 };
533
534 void omnplay_init(omnplay_instance_t* app)
535 {
536 int i;
537 pthread_mutexattr_t attr;
538
539 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
540
541 gtk_signal_connect( GTK_OBJECT( app->window ), "destroy",
542 GTK_SIGNAL_FUNC(on_main_window_delete_event), app);
543
544 /* create lock */
545 pthread_mutex_init(&app->players.lock, &attr);
546
547 /* create a omneon status thread */
548 for(i = 0; i < app->players.count; i++)
549 pthread_create(&app->players.item[i].thread, NULL,
550 omnplay_thread_proc, &app->players.item[i]);
551
552 /* create lock */
553 pthread_mutex_init(&app->playlist.lock, &attr);
554
555 /* attach buttons click */
556 for(i = 1; i < BUTTON_LAST; i++)
557 gtk_signal_connect(GTK_OBJECT(app->buttons[i]), "clicked",
558 GTK_SIGNAL_FUNC( on_button_click), app );
559
560 };
561
562 void omnplay_release(omnplay_instance_t* app)
563 {
564 int i;
565 void* r;
566
567 app->f_exit = 1;
568
569 for(i = 0; i < app->players.count; i++)
570 /* create a omneon status thread */
571 pthread_join(app->players.item[i].thread, &r);
572
573 /* destroy lock */
574 pthread_mutex_destroy(&app->players.lock);
575
576 /* destroy lock */
577 pthread_mutex_destroy(&app->playlist.lock);
578 };