d1dc90606ef418dcce1cf5ab5b3b6f2d3dbd1275
[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 int omnplay_get_content(omnplay_instance_t* app, playlist_item_t *items, int limit,
43 omnplay_get_content_cb_proc proc, void* data)
44 {
45 int r, c = 0;
46 OmPlrClipInfo clip_info;
47 char clip_name[omPlrMaxClipDirLen];
48
49 pthread_mutex_lock(&app->players.lock);
50
51 r = OmPlrClipGetFirst((OmPlrHandle)app->players.item[0].handle, clip_name, sizeof(clip_name));
52 for(; c < limit && !r;)
53 {
54 /* get clip info */
55 clip_info.maxMsTracks = 0;
56 clip_info.size = sizeof(clip_info);
57
58 r = OmPlrClipGetInfo((OmPlrHandle)app->players.item[0].handle, clip_name, &clip_info);
59
60 if(!r)
61 {
62 /* copy item props */
63 strncpy(items[c].id, clip_name, PATH_MAX);
64 items[c].in = clip_info.firstFrame;
65 items[c].dur = clip_info.lastFrame - clip_info.firstFrame;
66
67 /* callback */
68 pthread_mutex_unlock(&app->players.lock);
69 if(proc)
70 proc(app, &items[c], data);
71 pthread_mutex_lock(&app->players.lock);
72
73 c++;
74 };
75
76 r = OmPlrClipGetNext((OmPlrHandle)app->players.item[0].handle, clip_name, sizeof(clip_name));
77 };
78
79 pthread_mutex_unlock(&app->players.lock);
80
81 return c;
82 };
83
84
85 static gboolean on_main_window_delete_event( GtkWidget *widget, GdkEvent *event, gpointer user_data )
86 {
87 g_print ("delete event occurred [start]\n");
88 gdk_threads_leave();
89 omnplay_release((omnplay_instance_t*)user_data);
90 gdk_threads_enter();
91 g_print ("delete event occurred [finish]\n");
92
93 return FALSE;
94 }
95
96 static void on_main_window_destroy( GtkWidget *widget, gpointer user_data )
97 {
98 g_print ("destroy occurred\n");
99 gtk_main_quit();
100 }
101
102 omnplay_instance_t* omnplay_create(int argc, char** argv)
103 {
104 int i, c;
105 omnplay_instance_t* app;
106
107 /* prepare application instance */
108 app = (omnplay_instance_t*)malloc(sizeof(omnplay_instance_t));
109 memset(app, 0, sizeof(omnplay_instance_t));
110
111 /* load parameters from command line */
112 if(!omnplay_opt(argc, argv, app) && app->players.count)
113 app->window = ui_omnplay(app);
114 else
115 omnplay_usage();
116
117 return app;
118 };
119
120 void omnplay_destroy(omnplay_instance_t* app)
121 {
122 free(app);
123 };
124
125 static int find_index_of_playlist_item(omnplay_instance_t* app, int start, int idx)
126 {
127 while(1)
128 {
129 if(app->playlist.item[start].omn_idx == idx)
130 return start;
131
132 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_END)
133 break;
134
135 start++;
136 };
137
138 return -1;
139 };
140
141 static void omnplay_update_status(omnplay_player_t* player, OmPlrStatus *prev , OmPlrStatus *curr)
142 {
143 int idx;
144 char tc_cur[32], tc_rem[32], state[32], status[32];
145 const char *clip;
146
147 if(curr)
148 {
149 frames2tc(curr->pos - curr->minPos, 25.0, tc_cur);
150 frames2tc(curr->maxPos - curr->pos, 25.0, tc_rem);
151 strcpy(status, "ONLINE");
152 clip = curr->currClipName;
153
154 switch(curr->state)
155 {
156 case omPlrStateStopped: strcpy(state, "STOPPED"); break;
157 case omPlrStateCuePlay: strcpy(state, "CUE_PLAY"); break;
158 case omPlrStatePlay: strcpy(state, "PLAY"); break;
159 case omPlrStateCueRecord: strcpy(state, "CUE_RECORD"); break;
160 case omPlrStateRecord: strcpy(state, "RECORD"); break;
161 };
162 }
163 else
164 {
165 tc_cur[0] = 0;
166 tc_rem[0] = 0;
167 clip = "";
168 state[0] = 0;
169 strcpy(status, "OFFLINE");
170 };
171
172 /* update status in status page */
173 gdk_threads_enter();
174 gtk_label_set_text(GTK_LABEL (player->label_tc_cur), tc_cur);
175 gtk_label_set_text(GTK_LABEL (player->label_tc_rem), tc_rem);
176 gtk_label_set_text(GTK_LABEL (player->label_state), state);
177 gtk_label_set_text(GTK_LABEL (player->label_status), status);
178 gtk_label_set_text(GTK_LABEL (player->label_clip), clip);
179 gdk_flush();
180 gdk_threads_leave();
181
182 /* update remaining time */
183 gdk_threads_enter();
184 pthread_mutex_lock(&player->app->playlist.lock);
185 pthread_mutex_lock(&player->app->players.lock);
186 if(curr->state == omPlrStatePlay || curr->state == omPlrStateCuePlay)
187 {
188 idx = find_index_of_playlist_item(player->app, player->playlist_start, curr->currClipNum);
189 if(idx >= 0)
190 {
191 frames2tc(curr->currClipStartPos + curr->currClipLen - curr->pos, 25.0, tc_rem);
192 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
193 }
194 if(curr->currClipNum != prev->currClipNum && 1 != prev->numClips)
195 {
196 tc_rem[0] = 0;
197 idx = find_index_of_playlist_item(player->app, player->playlist_start, prev->currClipNum);
198 if(idx >= 0)
199 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
200 };
201 }
202 else
203 {
204 tc_rem[0] = 0;
205 idx = find_index_of_playlist_item(player->app, player->playlist_start, curr->currClipNum);
206 if(idx >= 0)
207 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
208 idx = find_index_of_playlist_item(player->app, player->playlist_start, prev->currClipNum);
209 if(idx >= 0)
210 omnplay_playlist_draw_item_rem(player->app, idx, tc_rem);
211 };
212 pthread_mutex_unlock(&player->app->players.lock);
213 pthread_mutex_unlock(&player->app->playlist.lock);
214 gdk_flush();
215 gdk_threads_leave();
216
217
218 memcpy(prev, curr, sizeof(OmPlrStatus));
219 };
220
221 static void* omnplay_thread_proc(void* data)
222 {
223 int r;
224 OmPlrStatus st_curr, st_prev;
225 omnplay_player_t* player = (omnplay_player_t*)data;
226
227 g_warning("omnplay_thread_proc\n");
228
229 /* connect */
230 pthread_mutex_lock(&player->app->players.lock);
231 r = OmPlrOpen(player->host, player->name, (OmPlrHandle*)&player->handle);
232 pthread_mutex_unlock(&player->app->players.lock);
233 if(r)
234 {
235 g_warning("ERROR: OmPlrOpen(%s, %s) failed with 0x%.8X\n",
236 player->host, player->name, r);
237
238 return (void*)r;
239 };
240
241 /* setup to do not reconnect */
242 pthread_mutex_lock(&player->app->players.lock);
243 OmPlrSetRetryOpen((OmPlrHandle)player->handle, 0);
244 pthread_mutex_unlock(&player->app->players.lock);
245
246 /* setup directory */
247 if(player->app->players.path[0])
248 {
249 pthread_mutex_lock(&player->app->players.lock);
250 r = OmPlrClipSetDirectory((OmPlrHandle)player->handle, player->app->players.path);
251 pthread_mutex_unlock(&player->app->players.lock);
252
253 if(r)
254 {
255 g_warning("ERROR: OmPlrClipSetDirectory(%s) failed with 0x%.8X\n",
256 player->app->players.path, r);
257
258 pthread_mutex_lock(&player->app->players.lock);
259 OmPlrClose((OmPlrHandle)player->handle);
260 pthread_mutex_unlock(&player->app->players.lock);
261
262 return (void*)r;
263 };
264 };
265
266 /* endless loop */
267 for(r = 0 ; !player->app->f_exit && !r;)
268 {
269 /* sleep */
270 #ifdef _WIN32
271 Sleep(100);
272 #else
273 usleep(100000);
274 #endif
275
276 /* get status */
277 pthread_mutex_lock(&player->app->players.lock);
278 st_curr.size = sizeof(OmPlrStatus);
279 r = OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &st_curr);
280 pthread_mutex_unlock(&player->app->players.lock);
281
282 if(r)
283 g_warning("ERROR: OmPlrGetPlayerStatus failed with 0x%.8X\n", r);
284 else
285 if(memcmp(&st_curr, &st_prev, sizeof(OmPlrStatus)))
286 omnplay_update_status(player, &st_prev , &st_curr);
287 };
288
289 pthread_mutex_lock(&player->app->players.lock);
290 OmPlrClose((OmPlrHandle)player->handle);
291 pthread_mutex_unlock(&player->app->players.lock);
292
293 return NULL;
294 };
295
296 void get_selected_items_playlist_proc(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
297 {
298 int idx, *list = (int*)data;
299 gtk_tree_model_get(model, iter, 7, &idx, -1);
300 list[list[0] + 1] = idx;
301 list[0] = list[0] + 1;
302 };
303
304 static int* get_selected_items_playlist(omnplay_instance_t* app)
305 {
306 int* list = NULL;
307 GtkTreeSelection *selection;
308
309 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid));
310 if(selection)
311 {
312 list = (int*)malloc(sizeof(int) * (MAX_PLAYLIST_ITEMS + 1));
313 memset(list, 0, sizeof(int) * (MAX_PLAYLIST_ITEMS + 1));
314
315 gtk_tree_selection_selected_foreach(
316 selection,
317 get_selected_items_playlist_proc,
318 list);
319
320 if(!list[0])
321 {
322 free(list);
323 list = NULL;
324 };
325 };
326
327 return list;
328 };
329
330 static int idx_in_players_range(omnplay_instance_t* app, int idx)
331 {
332 int i, r = 0;
333
334 for(i = 0; i < app->players.count && !r; i++)
335 {
336 int a, b;
337
338 a = app->players.item[i].playlist_start;
339 b = app->players.item[i].playlist_length;
340
341 if(b <= 0)
342 continue;
343
344 b = a + b - 1;
345
346 if(idx >= a && idx <= b) r = 1;
347 };
348
349 return r;
350 };
351
352 static int idxs_in_players_range(omnplay_instance_t* app, int start, int stop)
353 {
354 int i, r = 0;
355
356 for(i = 0; i < app->players.count && !r; i++)
357 {
358 int a, b;
359
360 a = app->players.item[i].playlist_start;
361 b = app->players.item[i].playlist_length;
362
363 if(b <= 0)
364 continue;
365
366 b = a + b - 1;
367
368 #define IN_RANGE(A,B,C) (A <= C && C <= B)
369 if( IN_RANGE(a,b,start) ||
370 IN_RANGE(a,b,stop) ||
371 IN_RANGE(start,stop,a) ||
372 IN_RANGE(start,stop,b))
373 r = 1;
374 };
375
376 return r;
377 };
378
379 static void omnplay_playlist_block(omnplay_instance_t* app, control_buttons_t button)
380 {
381 int start, stop, r, i;
382 int* list = get_selected_items_playlist(app);
383
384 if(!list)
385 return;
386
387 pthread_mutex_lock(&app->playlist.lock);
388 pthread_mutex_lock(&app->players.lock);
389
390 start = list[1];
391 stop = list[list[0]];
392
393 if(!idxs_in_players_range(app, start, stop))
394 {
395 int loop = (button == BUTTON_PLAYLIST_BLOCK_LOOP)?OMNPLAY_PLAYLIST_BLOCK_LOOP:0;
396
397 /* update selected item */
398 for(i = start; i <= stop; i++)
399 {
400 int t = OMNPLAY_PLAYLIST_BLOCK_BODY | loop;
401
402 if(i == start) t |= OMNPLAY_PLAYLIST_BLOCK_BEGIN;
403 if(i == stop) t |= OMNPLAY_PLAYLIST_BLOCK_END;
404
405 app->playlist.item[i].type = (playlist_item_type_t)t;
406
407 omnplay_playlist_draw_item(app, i);
408 };
409
410 /* update border items */
411 if(start && !(app->playlist.item[start - 1].type & OMNPLAY_PLAYLIST_BLOCK_END))
412 {
413 app->playlist.item[start - 1].type = (playlist_item_type_t)(OMNPLAY_PLAYLIST_BLOCK_END
414 | app->playlist.item[start - 1].type);
415 omnplay_playlist_draw_item(app, start - 1);
416 };
417 if((stop + 1) < app->playlist.count && !(app->playlist.item[stop + 1].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN))
418 {
419 app->playlist.item[stop + 1].type = (playlist_item_type_t)(OMNPLAY_PLAYLIST_BLOCK_BEGIN
420 | app->playlist.item[stop + 1].type);
421 omnplay_playlist_draw_item(app, stop + 1);
422 };
423 }
424 else
425 g_warning("omnplay_playlist_block: range [%d %d] do OVERLAP player\n",
426 start, stop);
427
428 pthread_mutex_unlock(&app->players.lock);
429 pthread_mutex_unlock(&app->playlist.lock);
430
431 free(list);
432 };
433
434 static int get_first_selected_item_playlist(omnplay_instance_t* app)
435 {
436 int idx;
437 int* list = get_selected_items_playlist(app);
438 if(!list) return -1;
439 idx = list[1];
440 free(list);
441 return idx;
442 };
443
444 static int get_playlist_block(omnplay_instance_t* app, int idx, int* start_ptr, int* stop_ptr)
445 {
446 int start, stop;
447
448 for(start = idx; start >= 0; start--)
449 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN)
450 break;
451
452 for(stop = idx; stop < app->playlist.count; stop++)
453 if(app->playlist.item[stop].type & OMNPLAY_PLAYLIST_BLOCK_END)
454 break;
455
456 g_warning("get_playlist_block: range %d -> %d\n", start, stop);
457
458 /* check block range */
459 if(start >= 0 && stop < app->playlist.count)
460 {
461 *start_ptr = start;
462 *stop_ptr = stop;
463 return (stop - start + 1);
464 };
465
466 return -1;
467 };
468
469 static omnplay_player_t *get_player_at_pos(omnplay_instance_t* app, int pos)
470 {
471 /* check player range */
472 if(app->playlist.item[pos].player > -1 && app->playlist.item[pos].player < app->players.count)
473 return &app->players.item[app->playlist.item[pos].player];
474
475 return NULL;
476 };
477
478 static void omnplay_playlist_delete_items(omnplay_instance_t* app, int* idxs, int count)
479 {
480 int i, j, idx;
481 GtkTreePath* path;
482
483 pthread_mutex_lock(&app->playlist.lock);
484 pthread_mutex_lock(&app->players.lock);
485
486 for(j = 0; j < count; j++)
487 {
488 idx = idxs[j] - j;
489
490 /* fix block types */
491 if( app->playlist.item[idx].type != OMNPLAY_PLAYLIST_ITEM_BLOCK_BODY &&
492 app->playlist.item[idx].type != OMNPLAY_PLAYLIST_ITEM_LOOP_BODY)
493 {
494 if(idx)
495 app->playlist.item[idx - 1].type = (playlist_item_type_t)(app->playlist.item[idx - 1].type |
496 OMNPLAY_PLAYLIST_BLOCK_END);
497 if(idx + 1 < app->playlist.count)
498 app->playlist.item[idx + 1].type = (playlist_item_type_t)(app->playlist.item[idx + 1].type |
499 OMNPLAY_PLAYLIST_BLOCK_BEGIN);
500 };
501
502 /* shift playlist items */
503 memmove
504 (
505 &app->playlist.item[idx],
506 &app->playlist.item[idx + 1],
507 (app->playlist.count - idx - 1) * sizeof(playlist_item_t)
508 );
509
510 /* decrement items count */
511 app->playlist.count--;
512
513 /* increment servers indexes */
514 for(i = 0; i < app->players.count; i++)
515 if(app->players.item[i].playlist_start >= idx)
516 app->players.item[i].playlist_start--;
517
518
519 };
520
521 /* redraw playlist */
522 omnplay_playlist_draw(app);
523
524 /* select */
525 path = gtk_tree_path_new_from_indices(idxs[0], -1);
526 gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid)), path);
527 gtk_tree_view_set_cursor(GTK_TREE_VIEW(app->playlist_grid), path, NULL, FALSE);
528 gtk_tree_path_free(path);
529
530
531 pthread_mutex_unlock(&app->players.lock);
532 pthread_mutex_unlock(&app->playlist.lock);
533 };
534
535 static void omnplay_playlist_item_del(omnplay_instance_t* app)
536 {
537 int i, idx, c;
538 int *list, *list2;
539
540 list = get_selected_items_playlist(app);
541 if(!list) return;
542
543 list2 = (int*)malloc(sizeof(int) * list[0]);
544
545 for(i = 0, c = 0; i < list[0]; i++)
546 {
547 /* check for playing block */
548 if(idx_in_players_range(app, list[i + 1]))
549 continue;
550
551 /* save index */
552 list2[c++] = list[i + 1];
553 };
554
555 if(c)
556 omnplay_playlist_delete_items(app, list2, c);
557
558 free(list2);
559 free(list);
560 };
561
562 static int omnplay_playlist_insert_check(omnplay_instance_t* app, int idx, playlist_item_type_t* t)
563 {
564 *t = OMNPLAY_PLAYLIST_ITEM_BLOCK_SINGLE;
565
566 /* before or after playlist */
567 if(!idx || idx == app->playlist.count)
568 return 1;
569
570 /* check for block borders */
571 if( app->playlist.item[idx - 1].type & OMNPLAY_PLAYLIST_BLOCK_END &&
572 app->playlist.item[idx + 0].type & OMNPLAY_PLAYLIST_BLOCK_BEGIN)
573 return 1;
574
575 /* check for playing block */
576 if(idx_in_players_range(app, idx))
577 return 0;
578
579 if(app->playlist.item[idx].type & OMNPLAY_PLAYLIST_BLOCK_LOOP)
580 *t = OMNPLAY_PLAYLIST_ITEM_LOOP_BODY;
581 else
582 *t = OMNPLAY_PLAYLIST_ITEM_BLOCK_BODY;
583
584 return 1;
585 };
586
587 static void omnplay_playlist_insert_items(omnplay_instance_t* app, int idx,
588 playlist_item_t* items, int count)
589 {
590 int i;
591 GtkTreePath* path;
592
593 pthread_mutex_lock(&app->playlist.lock);
594 pthread_mutex_lock(&app->players.lock);
595
596 /* shift playlist items */
597 memmove
598 (
599 &app->playlist.item[idx + count],
600 &app->playlist.item[idx],
601 (app->playlist.count - idx) * sizeof(playlist_item_t)
602 );
603
604 /* copy new items */
605 memcpy
606 (
607 &app->playlist.item[idx],
608 items,
609 count * sizeof(playlist_item_t)
610 );
611
612 /* increment servers indexes */
613 for(i = 0; i < app->players.count; i++)
614 if(app->players.item[i].playlist_start >= idx)
615 app->players.item[i].playlist_start += idx;
616
617 /* increment items count */
618 app->playlist.count += count;
619
620 /* redraw playlist */
621 omnplay_playlist_draw(app);
622
623 /* select */
624 path = gtk_tree_path_new_from_indices(idx + count, -1);
625 gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid)), path);
626 gtk_tree_view_set_cursor(GTK_TREE_VIEW(app->playlist_grid), path, NULL, FALSE);
627 gtk_tree_path_free(path);
628
629 pthread_mutex_unlock(&app->players.lock);
630 pthread_mutex_unlock(&app->playlist.lock);
631 };
632
633 static void omnplay_playlist_item_add(omnplay_instance_t* app, int after)
634 {
635 int idx;
636 playlist_item_t item;
637 playlist_item_type_t t;
638
639 /* find insert position */
640 idx = get_first_selected_item_playlist(app);
641 if(idx < 0)
642 idx = 0;
643 else
644 idx += (after)?1:0;
645
646 if(!omnplay_playlist_insert_check(app, idx, &t))
647 return;
648
649 g_warning("allowed insert into idx=%d\n", idx);
650
651 /* clear item */
652 memset(&item, 0, sizeof(playlist_item_t));
653 if(ui_playlist_item_dialog(app, &item))
654 {
655 omnplay_library_normalize_item(app, &item);
656 item.type = t;
657 omnplay_playlist_insert_items(app, idx, &item, 1);
658 };
659 };
660
661 static void omnplay_playlist_item_edit(omnplay_instance_t* app)
662 {
663 int idx;
664 playlist_item_t item;
665
666 /* find insert position */
667 idx = get_first_selected_item_playlist(app);
668
669 if(idx < 0)
670 return;
671
672 /* check for playing block */
673 if(idx_in_players_range(app, idx))
674 return;
675
676 item = app->playlist.item[idx];
677
678 if(ui_playlist_item_dialog(app, &item))
679 {
680 omnplay_library_normalize_item(app, &item);
681 app->playlist.item[idx] = item;
682 omnplay_playlist_draw_item(app, idx);
683 };
684 };
685
686 static void omnplay_ctl(omnplay_instance_t* app, control_buttons_t button)
687 {
688 int i, r;
689 int idx, start, stop;
690 omnplay_player_t *player;
691
692 pthread_mutex_lock(&app->playlist.lock);
693
694 idx = get_first_selected_item_playlist(app);
695
696 if(idx < 0)
697 {
698 pthread_mutex_unlock(&app->playlist.lock);
699 return;
700 };
701
702 g_warning("cue: selected item is %d\n", idx);
703
704 if(get_playlist_block(app, idx, &start, &stop) < 0)
705 {
706 pthread_mutex_unlock(&app->playlist.lock);
707 return;
708 };
709
710 g_warning("cue: range %d -> %d\n", start, stop);
711
712 player = get_player_at_pos(app, start);
713
714 if(!player)
715 {
716 pthread_mutex_unlock(&app->playlist.lock);
717 return;
718 };
719
720 pthread_mutex_lock(&app->players.lock);
721
722 if(BUTTON_PLAYER_STOP == button || BUTTON_PLAYER_CUE == button)
723 {
724 /* stop */
725 OmPlrStop((OmPlrHandle)player->handle);
726
727 /* detach previous clips */
728 player->playlist_length = -1;
729 OmPlrDetachAllClips((OmPlrHandle)player->handle);
730 };
731
732 if(BUTTON_PLAYER_CUE == button)
733 {
734 int o, c, p = 0;
735
736 /* Attach clips to timeline */
737 for(i = start, c = 0, o = 0; i <= stop; i++)
738 {
739 OmPlrClipInfo clip;
740
741 /* get clip info */
742 clip.maxMsTracks = 0;
743 clip.size = sizeof(clip);
744 r = OmPlrClipGetInfo((OmPlrHandle)player->handle, app->playlist.item[i].id, &clip);
745
746 if(!r)
747 {
748 unsigned int l;
749
750 g_warning("OmPlrClipGetInfo(%s): firstFrame=%d, lastFrame=%d\n",
751 app->playlist.item[i].id, clip.firstFrame, clip.lastFrame);
752
753 /* should we fix playlist clip timings */
754 if(!(
755 app->playlist.item[i].in >= clip.firstFrame &&
756 app->playlist.item[i].in + app->playlist.item[i].dur <= clip.lastFrame) ||
757 !app->playlist.item[i].dur)
758 {
759 g_warning("cue: item [%s] will be updated [%d;%d]->[%d;%d]\n",
760 app->playlist.item[i].id,
761 app->playlist.item[i].in, app->playlist.item[i].dur,
762 clip.firstFrame, clip.lastFrame - clip.firstFrame);
763
764 app->playlist.item[i].in = clip.firstFrame;
765 app->playlist.item[i].dur = clip.lastFrame - clip.firstFrame;
766 omnplay_playlist_draw_item(app, i);
767 };
768
769 r = OmPlrAttach((OmPlrHandle)player->handle,
770 app->playlist.item[i].id,
771 app->playlist.item[i].in,
772 app->playlist.item[i].in + app->playlist.item[i].dur,
773 0, omPlrShiftModeAfter, &l);
774 };
775
776 if(r)
777 {
778 g_warning("cue: failed with %d, %s\n", r, OmPlrGetErrorString((OmPlrError)r));
779 app->playlist.item[i].omn_idx = -1;
780 app->playlist.item[i].omn_offset = -1;
781 app->playlist.item[i].error |= PLAYLIST_ITEM_ERROR_CUE;
782 }
783 else
784 {
785 app->playlist.item[i].omn_idx = c;
786 app->playlist.item[i].omn_offset = o;
787 app->playlist.item[i].error &= 0xF ^ PLAYLIST_ITEM_ERROR_CUE;
788
789 /* save selected item offset */
790 if(i == idx) p = o;
791
792 c++;
793 o += app->playlist.item[i].dur;
794 };
795 };
796
797 if(c)
798 {
799 OmPlrStatus hs;
800
801 /* Set timeline min/max */
802 OmPlrSetMinPosMin((OmPlrHandle)player->handle);
803 OmPlrSetMaxPosMax((OmPlrHandle)player->handle);
804
805 /* Set timeline position */
806 hs.minPos = 0;
807 hs.size = sizeof(OmPlrStatus);
808 OmPlrGetPlayerStatus((OmPlrHandle)player->handle, &hs);
809 OmPlrSetPos((OmPlrHandle)player->handle, hs.minPos + p);
810
811 /* setup loop */
812 if(app->playlist.item[start].type & OMNPLAY_PLAYLIST_BLOCK_LOOP)
813 OmPlrLoop((OmPlrHandle)player->handle, hs.minPos, hs.maxPos);
814 else
815 OmPlrLoop((OmPlrHandle)player->handle, hs.minPos, hs.minPos);
816
817 player->playlist_start = start;
818 player->playlist_length = stop - start + 1;
819
820 /* Cue */
821 OmPlrCuePlay((OmPlrHandle)player->handle, 0.0);
822 };
823 };
824
825 if(BUTTON_PLAYER_PLAY == button)
826 {
827 /* play */
828 OmPlrPlay((OmPlrHandle)player->handle, 1.0);
829 };
830
831 if(BUTTON_PLAYER_PAUSE == button)
832 /* pause */
833 OmPlrPlay((OmPlrHandle)player->handle, 0.0);
834
835 pthread_mutex_unlock(&app->players.lock);
836
837 pthread_mutex_unlock(&app->playlist.lock);
838 };
839
840 static void omnplay_playlist_item_swap(omnplay_instance_t* app, int dir)
841 {
842 int sel, a, b, e = 1;
843 GtkTreePath* path;
844 playlist_item_t item;
845
846 /* find insert position */
847 sel = get_first_selected_item_playlist(app);
848 if(sel < 0)
849 return;
850
851 if(dir < 0)
852 {
853 a = sel - 1;
854 b = sel;
855 sel = a;
856 }
857 else
858 {
859 a = sel;
860 b = sel + 1;
861 sel = b;
862 };
863
864 /* check for playing block */
865 if(idx_in_players_range(app, a) || idx_in_players_range(app, b))
866 return;
867
868 pthread_mutex_lock(&app->playlist.lock);
869 pthread_mutex_lock(&app->players.lock);
870
871 /* swap */
872 item = app->playlist.item[a];
873 app->playlist.item[a] = app->playlist.item[b];
874 app->playlist.item[b] = item;
875
876 /* rewite type */
877 if(app->playlist.item[a].type != app->playlist.item[b].type)
878 {
879 e = 0;
880 app->playlist.item[a].type = OMNPLAY_PLAYLIST_ITEM_BLOCK_SINGLE;
881 app->playlist.item[b].type = OMNPLAY_PLAYLIST_ITEM_BLOCK_SINGLE;
882 };
883
884 /* redraw main items */
885 omnplay_playlist_draw_item(app, a);
886 omnplay_playlist_draw_item(app, b);
887
888 /* fix block types */
889 if(a && !e)
890 {
891 app->playlist.item[a - 1].type = (playlist_item_type_t)(app->playlist.item[a - 1].type |
892 OMNPLAY_PLAYLIST_BLOCK_END);
893 omnplay_playlist_draw_item(app, a - 1);
894 };
895 if(b + 1 < app->playlist.count && !e)
896 {
897 app->playlist.item[b + 1].type = (playlist_item_type_t)(app->playlist.item[b + 1].type |
898 OMNPLAY_PLAYLIST_BLOCK_BEGIN);
899 omnplay_playlist_draw_item(app, b + 1);
900 };
901
902 /* select */
903 path = gtk_tree_path_new_from_indices(sel, -1);
904 gtk_tree_selection_select_path(gtk_tree_view_get_selection(GTK_TREE_VIEW(app->playlist_grid)), path);
905 gtk_tree_view_set_cursor(GTK_TREE_VIEW(app->playlist_grid), path, NULL, FALSE);
906 gtk_tree_path_free(path);
907
908 pthread_mutex_unlock(&app->players.lock);
909 pthread_mutex_unlock(&app->playlist.lock);
910 };
911
912 static void omnplay_library_add(omnplay_instance_t* app, int after)
913 {
914 int idx, c, i;
915 playlist_item_t* items;
916 playlist_item_type_t t;
917
918 /* find insert position */
919 idx = get_first_selected_item_playlist(app);
920 if(idx < 0)
921 idx = 0;
922 else
923 idx += (after)?1:0;
924
925 if(!omnplay_playlist_insert_check(app, idx, &t))
926 return;
927
928 items = omnplay_library_get_selected(app, &c);
929
930 /* clear item */
931 if(items)
932 {
933 for(i = 0; i < c; i++)
934 {
935 items[i].type = t;
936 items[i].error = 0;
937 };
938 omnplay_playlist_insert_items(app, idx, items, c);
939 };
940 };
941
942
943 static gboolean omnplay_button_click(omnplay_instance_t* app, control_buttons_t button)
944 {
945 switch(button)
946 {
947 case BUTTON_PLAYLIST_ITEM_ADD:
948 omnplay_playlist_item_add(app, 0);
949 break;
950 case BUTTON_PLAYLIST_ITEM_DEL:
951 omnplay_playlist_item_del(app);
952 break;
953 case BUTTON_PLAYLIST_ITEM_EDIT:
954 omnplay_playlist_item_edit(app);
955 break;
956 case BUTTON_PLAYLIST_LOAD:
957 omnplay_playlist_load(app);
958 break;
959 case BUTTON_PLAYLIST_SAVE:
960 omnplay_playlist_save(app);
961 break;
962 case BUTTON_PLAYLIST_BLOCK_SINGLE:
963 case BUTTON_PLAYLIST_BLOCK_LOOP:
964 omnplay_playlist_block(app, button);
965 break;
966 case BUTTON_PLAYLIST_ITEM_UP:
967 omnplay_playlist_item_swap(app, -1);
968 break;
969 case BUTTON_PLAYLIST_ITEM_DOWN:
970 omnplay_playlist_item_swap(app, +1);
971 break;
972 case BUTTON_PLAYER_CUE:
973 case BUTTON_PLAYER_PLAY:
974 case BUTTON_PLAYER_PAUSE:
975 case BUTTON_PLAYER_STOP:
976 omnplay_ctl(app, button);
977 break;
978 case BUTTON_LIBRARY_ADD:
979 omnplay_library_add(app, 0);
980 break;
981 case BUTTON_LIBRARY_REFRESH:
982 omnplay_library_refresh(app);
983 break;
984 case BUTTON_LIBRARY_FIND:
985 omnplay_library_search(app, 0);
986 break;
987 case BUTTON_LIBRARY_FIND_NEXT:
988 omnplay_library_search(app, 1);
989 break;
990 };
991
992 return TRUE;
993 };
994
995 static gboolean on_button_click(GtkWidget *button, gpointer user_data)
996 {
997 int i;
998 omnplay_instance_t* app = (omnplay_instance_t*)user_data;
999
1000 for(i = 1; i < BUTTON_LAST; i++)
1001 if(app->buttons[i] == button)
1002 return omnplay_button_click(app, (control_buttons_t)i);
1003
1004 return FALSE;
1005 };
1006
1007 static void omnplay_playlist_item_copy(omnplay_instance_t* app)
1008 {
1009 int *list, i;
1010
1011 list = get_selected_items_playlist(app);
1012 if(!list) return;
1013
1014 for(i = 0; i < list[0]; i++)
1015 app->clipboard.item[i] = app->playlist.item[list[i + 1]];
1016 app->clipboard.count = list[0];
1017
1018 free(list);
1019 };
1020
1021 static void omnplay_playlist_item_paste(omnplay_instance_t* app, int after)
1022 {
1023 int idx, i;
1024 playlist_item_t* items;
1025 playlist_item_type_t t;
1026
1027 /* find insert position */
1028 idx = get_first_selected_item_playlist(app);
1029 if(idx < 0)
1030 idx = 0;
1031 else
1032 idx += (after)?1:0;
1033
1034 if(!omnplay_playlist_insert_check(app, idx, &t))
1035 return;
1036
1037 /* clear item */
1038 if(app->clipboard.count)
1039 {
1040 for(i = 0; i < app->clipboard.count; i++)
1041 {
1042 app->clipboard.item[i].type = t;
1043 app->clipboard.item[i].error = 0;
1044 };
1045 omnplay_playlist_insert_items(app, idx, app->clipboard.item, app->clipboard.count);
1046 };
1047 };
1048
1049 static gboolean on_playlist_grid_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
1050 {
1051 omnplay_instance_t* app = (omnplay_instance_t*)data;
1052
1053 switch(event->keyval)
1054 {
1055 case GDK_C:
1056 case GDK_c:
1057 if(event->state & GDK_CONTROL_MASK)
1058 {
1059 omnplay_playlist_item_copy(app);
1060 return TRUE;
1061 };
1062 break;
1063 case GDK_V:
1064 case GDK_v:
1065 if(event->state & GDK_CONTROL_MASK)
1066 {
1067 omnplay_playlist_item_paste(app, 0);
1068 return TRUE;
1069 };
1070 break;
1071 case GDK_X:
1072 case GDK_x:
1073 if(event->state & GDK_CONTROL_MASK)
1074 {
1075 omnplay_playlist_item_copy(app);
1076 omnplay_playlist_item_del(app);
1077 return TRUE;
1078 };
1079 break;
1080 case GDK_S:
1081 case GDK_s:
1082 if(event->state & GDK_CONTROL_MASK)
1083 {
1084 omnplay_playlist_save(app);
1085 return TRUE;
1086 };
1087 break;
1088 case GDK_O:
1089 case GDK_o:
1090 if(event->state & GDK_CONTROL_MASK)
1091 {
1092 omnplay_playlist_load(app);
1093 return TRUE;
1094 };
1095 break;
1096 case GDK_KEY_space:
1097 omnplay_ctl(app, BUTTON_PLAYER_PLAY);
1098 return TRUE;
1099 case GDK_KEY_Return:
1100 omnplay_ctl(app, BUTTON_PLAYER_CUE);
1101 return TRUE;
1102 case GDK_KEY_Insert:
1103 omnplay_playlist_item_add(app, 0);
1104 return TRUE;
1105 case GDK_KEY_Delete:
1106 omnplay_playlist_item_del(app);
1107 return TRUE;
1108 case GDK_KEY_BackSpace:
1109 omnplay_playlist_item_edit(app);
1110 return TRUE;
1111 };
1112
1113 return FALSE;
1114 };
1115
1116 static gboolean on_library_grid_key(GtkWidget *widget, GdkEventKey *event, gpointer data)
1117 {
1118 omnplay_instance_t* app = (omnplay_instance_t*)data;
1119
1120 switch(event->keyval)
1121 {
1122 case GDK_C:
1123 case GDK_c:
1124 if(event->state & GDK_CONTROL_MASK)
1125 {
1126 int count;
1127 playlist_item_t* items;
1128
1129 items = omnplay_library_get_selected(app, &count);
1130
1131 if(items)
1132 {
1133 int i;
1134
1135 for(i = 0; i < count; i++)
1136 app->clipboard.item[i] = items[i];
1137
1138 app->clipboard.count = count;
1139 };
1140
1141 return TRUE;
1142 };
1143 break;
1144 case GDK_V:
1145 case GDK_v:
1146 if(event->state & GDK_CONTROL_MASK)
1147 {
1148 g_warning("CTRL+v\n");
1149 return TRUE;
1150 };
1151 break;
1152 case GDK_X:
1153 case GDK_x:
1154 if(event->state & GDK_CONTROL_MASK)
1155 {
1156 g_warning("CTRL+x\n");
1157 return TRUE;
1158 };
1159 break;
1160 };
1161
1162 return FALSE;
1163 };
1164
1165 static gboolean on_library_grid_button(GtkWidget *widget, GdkEventButton *event, gpointer data)
1166 {
1167 // g_warning("on_library_grid_button: event->button=%d, event->type=%d", event->button, event->type);
1168
1169 if(event->button==1 && event->type==GDK_2BUTTON_PRESS)
1170 {
1171 omnplay_library_add((omnplay_instance_t* )data, 0);
1172 return TRUE;
1173 };
1174
1175 return FALSE;
1176 };
1177
1178 static gboolean on_playlist_grid_button(GtkWidget *widget, GdkEventButton *event, gpointer data)
1179 {
1180 // g_warning("on_playlist_grid_button");
1181
1182 if(event->button==1 && event->type==GDK_2BUTTON_PRESS)
1183 {
1184 omnplay_ctl((omnplay_instance_t* )data, BUTTON_PLAYER_CUE);
1185 return TRUE;
1186 };
1187
1188 return FALSE;
1189 };
1190
1191 void omnplay_init(omnplay_instance_t* app)
1192 {
1193 int i;
1194 pthread_mutexattr_t attr;
1195
1196 pthread_mutexattr_init(&attr);
1197 pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
1198
1199 gtk_signal_connect( GTK_OBJECT( app->window ), "delete-event",
1200 GTK_SIGNAL_FUNC(on_main_window_delete_event), app);
1201
1202 gtk_signal_connect( GTK_OBJECT( app->window ), "destroy",
1203 GTK_SIGNAL_FUNC(on_main_window_destroy), app);
1204
1205 gtk_widget_add_events(app->playlist_grid, GDK_BUTTON_PRESS_MASK);
1206 gtk_widget_add_events(app->playlist_grid, GDK_KEY_PRESS_MASK);
1207 gtk_signal_connect(GTK_OBJECT(app->playlist_grid), "key-press-event",
1208 GTK_SIGNAL_FUNC(on_playlist_grid_key), app);
1209
1210 gtk_widget_add_events(app->library_grid, GDK_BUTTON_PRESS_MASK);
1211 gtk_widget_add_events(app->library_grid, GDK_KEY_PRESS_MASK);
1212 gtk_signal_connect(GTK_OBJECT(app->library_grid), "key-press-event",
1213 GTK_SIGNAL_FUNC(on_library_grid_key), app);
1214
1215 gtk_signal_connect(GTK_OBJECT(app->playlist_grid), "button-press-event",
1216 GTK_SIGNAL_FUNC(on_playlist_grid_button), app);
1217
1218 gtk_signal_connect(GTK_OBJECT(app->library_grid), "button-press-event",
1219 GTK_SIGNAL_FUNC(on_library_grid_button), app);
1220
1221 /* create lock */
1222 pthread_mutex_init(&app->players.lock, &attr);
1223
1224 /* create a omneon status thread */
1225 for(i = 0; i < app->players.count; i++)
1226 pthread_create(&app->players.item[i].thread, NULL,
1227 omnplay_thread_proc, &app->players.item[i]);
1228
1229
1230 /* create lock */
1231 pthread_mutex_init(&app->playlist.lock, &attr);
1232
1233 /* attach buttons click */
1234 for(i = 1; i < BUTTON_LAST; i++)
1235 gtk_signal_connect(GTK_OBJECT(app->buttons[i]), "clicked",
1236 GTK_SIGNAL_FUNC( on_button_click), app );
1237
1238 /* create lock */
1239 pthread_mutex_init(&app->library.lock, &attr);
1240
1241 /* load library */
1242 omnplay_library_load(app);
1243
1244 pthread_mutexattr_destroy(&attr);
1245 };
1246
1247 void omnplay_release(omnplay_instance_t* app)
1248 {
1249 int i;
1250 void* r;
1251
1252 app->f_exit = 1;
1253
1254 for(i = 0; i < app->players.count; i++)
1255 /* create a omneon status thread */
1256 pthread_join(app->players.item[i].thread, &r);
1257
1258 /* destroy lock */
1259 pthread_mutex_destroy(&app->players.lock);
1260
1261 /* destroy lock */
1262 pthread_mutex_destroy(&app->playlist.lock);
1263
1264 /* load library */
1265 omnplay_library_save(app);
1266
1267 /* destroy library lock */
1268 pthread_mutex_destroy(&app->library.lock);
1269 };
1270
1271 void omnplay_playlist_normalize(omnplay_instance_t* app)
1272 {
1273 int i;
1274
1275 /* normalize playlist */
1276 for(i = 0; i < app->playlist.count; i++)
1277 if(omnplay_library_normalize_item(app, &app->playlist.item[i]))
1278 omnplay_playlist_draw_item(app, i);
1279 };