8c6d525bb7e9810422b93a86dc06bf95cc8ebbda
[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 void omnplay_update_status(omnplay_player_t* player, OmPlrStatus *prev , OmPlrStatus *curr)
72 {
73 char tc_cur[32], tc_rem[32], state[32], status[32];
74 const char *clip;
75
76 if(curr)
77 {
78 frames2tc(curr->pos - curr->minPos, 25.0, tc_cur);
79 frames2tc(curr->maxPos - curr->pos, 25.0, tc_rem);
80 strcpy(status, "ONLINE");
81 clip = curr->currClipName;
82
83 switch(curr->state)
84 {
85 case omPlrStateStopped: strcpy(state, "STOPPED"); break;
86 case omPlrStateCuePlay: strcpy(state, "CUE_PLAY"); break;
87 case omPlrStatePlay: strcpy(state, "PLAY"); break;
88 case omPlrStateCueRecord: strcpy(state, "CUE_RECORD"); break;
89 case omPlrStateRecord: strcpy(state, "RECORD"); break;
90 };
91 }
92 else
93 {
94 tc_cur[0] = 0;
95 tc_rem[0] = 0;
96 clip = "";
97 state[0] = 0;
98 strcpy(status, "OFFLINE");
99 };
100
101 gdk_threads_enter();
102 gtk_label_set_text(GTK_LABEL (player->label_tc_cur), tc_cur);
103 gtk_label_set_text(GTK_LABEL (player->label_tc_rem), tc_rem);
104 gtk_label_set_text(GTK_LABEL (player->label_state), state);
105 gtk_label_set_text(GTK_LABEL (player->label_status), status);
106 gtk_label_set_text(GTK_LABEL (player->label_clip), clip);
107 gdk_flush();
108 gdk_threads_leave();
109
110 memcpy(prev, curr, sizeof(OmPlrStatus));
111 };
112
113 static void* omnplay_thread_proc(void* data)
114 {
115 int r;
116 OmPlrStatus st_curr, st_prev;
117 omnplay_player_t* player = (omnplay_player_t*)data;
118
119 /* connect */
120 pthread_mutex_lock(&player->app->players.lock);
121 r = OmPlrOpen(player->host, player->name, (OmPlrHandle*)&player->handle);
122 pthread_mutex_unlock(&player->app->players.lock);
123 if(r)
124 {
125 fprintf(stderr, "ERROR: OmPlrOpen(%s, %s) failed with 0x%.8X\n",
126 player->host, player->name, r);
127
128 return (void*)r;
129 };
130
131 /* setup to do not reconnect */
132 pthread_mutex_lock(&player->app->players.lock);
133 OmPlrSetRetryOpen((OmPlrHandle)player->handle, 0);
134 pthread_mutex_unlock(&player->app->players.lock);
135
136 /* setup directory */
137 if(player->app->players.path[0])
138 {
139 pthread_mutex_lock(&player->app->players.lock);
140 r = OmPlrClipSetDirectory((OmPlrHandle)player->handle, player->app->players.path);
141 pthread_mutex_unlock(&player->app->players.lock);
142
143 if(r)
144 {
145 fprintf(stderr, "ERROR: OmPlrClipSetDirectory(%s) failed with 0x%.8X\n",
146 player->app->players.path, r);
147
148 pthread_mutex_lock(&player->app->players.lock);
149 OmPlrClose((OmPlrHandle)player->handle);
150 pthread_mutex_unlock(&player->app->players.lock);
151
152 return (void*)r;
153 };
154 };
155
156 /* endless loop */
157 for(r = 0 ; !player->app->f_exit && !r;)
158 {
159 /* sleep */
160 usleep(100000);
161
162 /* get status */
163 pthread_mutex_lock(&player->app->players.lock);
164 st_curr.size = sizeof(OmPlrStatus);
165 r = OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &st_curr);
166 pthread_mutex_unlock(&player->app->players.lock);
167
168 if(r)
169 fprintf(stderr, "ERROR: OmPlrGetPlayerStatus failed with 0x%.8X\n", r);
170 else
171 if(memcmp(&st_curr, &st_prev, sizeof(OmPlrStatus)))
172 omnplay_update_status(player, &st_prev , &st_curr);
173 };
174
175 pthread_mutex_lock(&player->app->players.lock);
176 OmPlrClose((OmPlrHandle)player->handle);
177 pthread_mutex_unlock(&player->app->players.lock);
178
179 return NULL;
180 };
181
182 static int get_first_selected_item_playlist(omnplay_instance_t* app)
183 {
184 int idx;
185 GtkTreeIter iter;
186 GtkTreeModel *model;
187 GtkTreeSelection *selection;
188
189 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid));
190 if(selection && gtk_tree_selection_get_selected(selection, &model, &iter))
191 {
192 gtk_tree_model_get(model, &iter, 7, &idx, -1);
193 return idx;
194 };
195 return -1;
196 };
197
198 static int get_playlist_block(omnplay_instance_t* app, int idx, int* start_ptr, int* stop_ptr)
199 {
200 int start, stop;
201
202 for(start = idx; start >= 0; start--)
203 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN)
204 break;
205
206 for(stop = idx; stop < app->playlist.count; stop++)
207 if(app->playlist.item[stop].type & OMNPLAY_PLAYLIST_BLOCK_END)
208 break;
209
210 fprintf(stderr, "get_playlist_block: range %d -> %d\n", start, stop);
211
212 /* check block range */
213 if(start >= 0 && stop < app->playlist.count)
214 {
215 *start_ptr = start;
216 *stop_ptr = stop;
217 return (stop - start + 1);
218 };
219
220 return -1;
221 };
222
223 static omnplay_player_t *get_player_at_pos(omnplay_instance_t* app, int pos)
224 {
225 /* check player range */
226 if(app->playlist.item[pos].player > -1 && app->playlist.item[pos].player < app->players.count)
227 return &app->players.item[app->playlist.item[pos].player];
228
229 return NULL;
230 };
231
232 static void omnplay_ctl(omnplay_instance_t* app, control_buttons_t button)
233 {
234 int i, r;
235 int idx, start, stop;
236 omnplay_player_t *player;
237
238 pthread_mutex_lock(&app->playlist.lock);
239
240 idx = get_first_selected_item_playlist(app);
241
242 if(idx < 0)
243 {
244 pthread_mutex_unlock(&app->playlist.lock);
245 return;
246 };
247
248 fprintf(stderr, "cue: selected item is %d\n", idx);
249
250 if(get_playlist_block(app, idx, &start, &stop) < 0)
251 {
252 pthread_mutex_unlock(&app->playlist.lock);
253 return;
254 };
255
256 fprintf(stderr, "cue: range %d -> %d\n", start, stop);
257
258 player = get_player_at_pos(app, start);
259
260 if(!player)
261 {
262 pthread_mutex_unlock(&app->playlist.lock);
263 return;
264 };
265
266 pthread_mutex_lock(&app->players.lock);
267
268 if(BUTTON_PLAYER_STOP == button || BUTTON_PLAYER_CUE == button)
269 {
270 /* stop */
271 OmPlrStop((OmPlrHandle)player->handle);
272
273 /* detach previous clips */
274 player->playlist_start = -1;
275 player->playlist_count = -1;
276 OmPlrDetachAllClips((OmPlrHandle)player->handle);
277 };
278
279 if(BUTTON_PLAYER_CUE == button)
280 {
281 int o, c, p = 0;
282
283 /* Attach clips to timeline */
284 for(i = start, c = 0, o = 0; i <= stop; i++)
285 {
286 OmPlrClipInfo clip;
287
288 /* get clip info */
289 clip.maxMsTracks = 0;
290 clip.size = sizeof(clip);
291 r = OmPlrClipGetInfo((OmPlrHandle)player->handle, app->playlist.item[i].id, &clip);
292
293 if(!r)
294 {
295 unsigned int l;
296
297 fprintf(stderr, "OmPlrClipGetInfo(%s): firstFrame=%d, lastFrame=%d\n",
298 app->playlist.item[i].id, clip.firstFrame, clip.lastFrame);
299
300 /* should we fix playlist clip timings */
301 if(!(
302 app->playlist.item[i].in >= clip.firstFrame &&
303 app->playlist.item[i].in + app->playlist.item[i].dur <= clip.lastFrame) ||
304 !app->playlist.item[i].dur)
305 {
306 fprintf(stderr, "cue: item [%s] will be updated [%d;%d]->[%d;%d]\n",
307 app->playlist.item[i].id,
308 app->playlist.item[i].in, app->playlist.item[i].dur,
309 clip.firstFrame, clip.lastFrame - clip.firstFrame);
310
311 app->playlist.item[i].in = clip.firstFrame;
312 app->playlist.item[i].dur = clip.lastFrame - clip.firstFrame;
313 omnplay_playlist_draw_item(app, i);
314 };
315
316 r = OmPlrAttach((OmPlrHandle)player->handle,
317 app->playlist.item[i].id,
318 app->playlist.item[i].in,
319 app->playlist.item[i].in + app->playlist.item[i].dur,
320 0, omPlrShiftModeAfter, &l);
321 };
322
323 if(r)
324 {
325 fprintf(stderr, "cue: failed with %d, %s\n", r, OmPlrGetErrorString((OmPlrError)r));
326 app->playlist.item[i].omn_idx = -1;
327 app->playlist.item[i].omn_offset = -1;
328 }
329 else
330 {
331 app->playlist.item[i].omn_idx = c;
332 app->playlist.item[i].omn_offset = o;
333
334 /* save selected item offset */
335 if(i == idx) p = o;
336
337 c++;
338 o += app->playlist.item[i].dur;
339 };
340 };
341
342 if(c)
343 {
344 OmPlrStatus hs;
345
346 /* Set timeline min/max */
347 OmPlrSetMinPosMin((OmPlrHandle)player->handle);
348 OmPlrSetMaxPosMax((OmPlrHandle)player->handle);
349
350 /* Set timeline position */
351 hs.minPos = 0;
352 hs.size = sizeof(OmPlrStatus);
353 OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &hs);
354 OmPlrSetPos((OmPlrHandle)player->handle, hs.minPos + p);
355
356 /* Cue */
357 OmPlrCuePlay((OmPlrHandle)player->handle, 0.0);
358 OmPlrPlay((OmPlrHandle)player->handle, 0.0);
359
360 player->playlist_start = start;
361 player->playlist_count = stop - start + 1;
362 };
363 };
364
365 if(BUTTON_PLAYER_PLAY == button)
366 {
367 /* play */
368 OmPlrPlay((OmPlrHandle)player->handle, 1.0);
369 };
370
371 if(BUTTON_PLAYER_PAUSE == button)
372 /* pause */
373 OmPlrPlay((OmPlrHandle)player->handle, 0.0);
374
375 pthread_mutex_unlock(&app->players.lock);
376
377 pthread_mutex_unlock(&app->playlist.lock);
378 };
379
380 static gboolean omnplay_button_click(omnplay_instance_t* app, control_buttons_t button)
381 {
382 switch(button)
383 {
384 case BUTTON_PLAYLIST_ITEM_ADD:
385 case BUTTON_PLAYLIST_ITEM_DEL:
386 case BUTTON_PLAYLIST_ITEM_EDIT:
387 break;
388 case BUTTON_PLAYLIST_LOAD:
389 omnplay_playlist_load(app);
390 break;
391 case BUTTON_PLAYLIST_SAVE:
392 omnplay_playlist_save(app);
393 break;
394 case BUTTON_PLAYLIST_BLOCK_SINGLE:
395 case BUTTON_PLAYLIST_BLOCK_LOOP:
396 break;
397 case BUTTON_PLAYLIST_ITEM_UP:
398 case BUTTON_PLAYLIST_ITEM_DOWN:
399 break;
400 case BUTTON_PLAYER_CUE:
401 case BUTTON_PLAYER_PLAY:
402 case BUTTON_PLAYER_PAUSE:
403 case BUTTON_PLAYER_STOP:
404 omnplay_ctl(app, button);
405 break;
406 case BUTTON_LIBRARY_ADD:
407 case BUTTON_LIBRARY_REFRESH:
408 break;
409 };
410
411 return TRUE;
412 };
413
414 static gboolean on_button_click(GtkWidget *button, gpointer user_data)
415 {
416 int i;
417 omnplay_instance_t* app = (omnplay_instance_t*)user_data;
418
419 for(i = 1; i < BUTTON_LAST; i++)
420 if(app->buttons[i] == button)
421 return omnplay_button_click(app, (control_buttons_t)i);
422
423 return FALSE;
424 };
425
426 void omnplay_init(omnplay_instance_t* app)
427 {
428 int i;
429 pthread_mutexattr_t attr;
430
431 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
432
433 gtk_signal_connect( GTK_OBJECT( app->window ), "destroy",
434 GTK_SIGNAL_FUNC(on_main_window_delete_event), app);
435
436 /* create lock */
437 pthread_mutex_init(&app->players.lock, &attr);
438
439 /* create a omneon status thread */
440 for(i = 0; i < app->players.count; i++)
441 pthread_create(&app->players.item[i].thread, NULL,
442 omnplay_thread_proc, &app->players.item[i]);
443
444 /* create lock */
445 pthread_mutex_init(&app->playlist.lock, &attr);
446
447 /* attach buttons click */
448 for(i = 1; i < BUTTON_LAST; i++)
449 gtk_signal_connect(GTK_OBJECT(app->buttons[i]), "clicked",
450 GTK_SIGNAL_FUNC( on_button_click), app );
451
452 };
453
454 void omnplay_release(omnplay_instance_t* app)
455 {
456 int i;
457 void* r;
458
459 app->f_exit = 1;
460
461 for(i = 0; i < app->players.count; i++)
462 /* create a omneon status thread */
463 pthread_join(app->players.item[i].thread, &r);
464
465 /* destroy lock */
466 pthread_mutex_destroy(&app->players.lock);
467
468 /* destroy lock */
469 pthread_mutex_destroy(&app->playlist.lock);
470 };