/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file ar e subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Initial Developer of the Original Code is * CSIRO * Portions created by the Initial Developer are Copyright (C) 2007 * the Initial Developer. All Rights Reserved. * * Contributor(s): Shane Stephens, Michael Martin * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include #include "std_semaphore.h" #include #include #include #include #include #include #include #include #include #include #include #include "plugin_tools.h" #include "sydney_audio.h" #define OGGPLAY_FRAME_SKIP_OFFSET -30 #define OGGPLAY_MIN_OFFSET 3 typedef struct { int width; int height; GtkWidget * window; pthread_t thread; int shutdown_gui; void * ogg_handle; gint timeout_id; semaphore oggplay_replace_sem; semaphore start_stop_sem; PluginPlaybackState playback_state; int64_t playback_target; int playback_finished; void * new_oggplay_handle; sa_stream_t * audio_handle; gboolean audio_opened; float volume; nsPluginInstance * pluginInstance; } PluginWindowInfo; void open_audio(PluginWindowInfo *info) { info->audio_opened = TRUE; info->playback_target = 0; if (sa_stream_create_pcm(&(info->audio_handle), "liboggplay browser plugin", SA_MODE_WRONLY, SA_PCM_FORMAT_S16_LE, get_audio_rate(info->ogg_handle), get_audio_channels(info->ogg_handle)) != SA_SUCCESS ) { info->audio_opened = FALSE; info->audio_handle = NULL; return; } if (sa_stream_open(info->audio_handle) != SA_SUCCESS) { sa_stream_destroy(info->audio_handle); info->audio_opened = FALSE; info->audio_handle = NULL; return; } return; } void close_audio(PluginWindowInfo *info) { if (info->audio_handle != NULL) { sa_stream_destroy(info->audio_handle); info->audio_handle = NULL; info->audio_opened = FALSE; info->playback_target = 0; printf("Audio device closed\n"); } } int switch_oggplays(PluginWindowInfo *info) { void * old_ogg_handle; if (info->new_oggplay_handle != NULL) { SEM_WAIT(info->oggplay_replace_sem); old_ogg_handle = info->ogg_handle; info->ogg_handle = (void *)info->new_oggplay_handle; info->new_oggplay_handle = NULL; info->playback_finished = FALSE; close_audio(info); SEM_SIGNAL(info->oggplay_replace_sem); shut_oggplay(old_ogg_handle); return TRUE; } return FALSE; } void render_frame_to_screen(PluginWindowInfo * info, PluginOggFrame *frame_data) { Imlib_Image image; convert_oggplay_frame(info->ogg_handle, frame_data, BGR); image = imlib_create_image_using_data(frame_data->width, frame_data->height, (unsigned int *)frame_data->frame); imlib_context_set_image(image); imlib_render_image_on_drawable_at_size(0, 0, info->width, info->height); imlib_free_image_and_decache(); } gboolean update_frame(gpointer _info) { PluginWindowInfo * info = (PluginWindowInfo *)_info; int64_t offset; PluginOggFrame frame_data; int64_t bytes; int64_t ref_time; frame_data.frame = NULL; frame_data.samples = NULL; frame_data.cmml_strings = NULL; frame_data.size = 0; switch_oggplays(info); /* * if we are paused, then don't do anything */ if (info->playback_state == PAUSED || info->playback_finished) { sa_stream_pause(info->audio_handle); return TRUE; } get_oggplay_frame(info->ogg_handle, &frame_data); switch (get_oggplay_stream_info(info->ogg_handle, &frame_data)) { case OGGPLAY_STREAM_JUST_SEEKED: close_audio(info); break; case OGGPLAY_STREAM_LAST_DATA: info->playback_finished = TRUE; close_audio(info); onEndOfMovie(info->pluginInstance); return TRUE; default: break; } /* if there is need to update plugin GUI then go to next frame */ if (frame_data.video_data == NULL && frame_data.samples == NULL) { return TRUE; } if (frame_data.video_data != NULL) { render_frame_to_screen(info, &frame_data); } /* below: audio, timing calculations and frame skipping */ calc_offset: if (frame_data.samples != NULL) { if (info->audio_opened == FALSE) { open_audio(info); } if (info->audio_opened == TRUE) { /* apply volume */ if (frame_data.size > 0 && info->volume < 1.0) { short * samples = (short *)frame_data.samples; int i; for (i = 0; i < frame_data.size/sizeof(short); i ++) { samples[i] *= info->volume; } } /* wirte audio */ if (sa_stream_write(info->audio_handle, frame_data.samples, frame_data.size) != SA_SUCCESS) { /* can't write data */ close_audio(info); } } } /* call CMML data callback using plugin's main thread call */ if (frame_data.cmml_strings != NULL) { onCMMLData(info->pluginInstance, frame_data.cmml_strings, frame_data.cmml_size, 0); } if (info->audio_opened == TRUE) { // calculate audio playback progress based on number of audio samples played if (sa_stream_get_position(info->audio_handle, SA_POSITION_WRITE_SOFTWARE, &bytes) != SA_SUCCESS) { /* can't get progress */ ref_time = (info->playback_target >> 16); } else { /* calculate time from playback bytepos */ ref_time = bytes * 1000 / get_audio_rate(info->ogg_handle) / (sizeof(short) * get_audio_channels(info->ogg_handle)); ref_time += (get_audio_rate(info->ogg_handle) * get_audio_channels(info->ogg_handle)) / 1000; } } else { ref_time = (info->playback_target >> 16); } /* clean up */ free_oggplay_frame(info->ogg_handle, &frame_data); /* calculate how long we should wait with processing the next frame */ info->playback_target += get_callback_period(info->ogg_handle); offset = (info->playback_target >> 16) - ref_time; /* printf("offset: %lld target: %lld ref_time %lld\n", offset, info->playback_target >> 16, ref_time); */ /* check if we are not lagging with display */ if (offset < OGGPLAY_FRAME_SKIP_OFFSET) { get_oggplay_frame(info->ogg_handle, &frame_data); goto calc_offset; } else if (offset < OGGPLAY_MIN_OFFSET) { offset = OGGPLAY_MIN_OFFSET; } /* * set new timeout and invalidate old one */ info->timeout_id = g_timeout_add((int)offset, update_frame, (gpointer)info); return FALSE; } gboolean thread_delete(GtkWidget * widget, GdkEvent * event, gpointer user_data) { PluginWindowInfo * info = (PluginWindowInfo *)user_data; g_source_remove(info->timeout_id); gtk_main_quit(); return 1; } gboolean display_signal (gpointer _info) { PluginWindowInfo * info = (PluginWindowInfo *)_info; SEM_SIGNAL(info->start_stop_sem); return FALSE; } void * display_thread(void *_info) { PluginWindowInfo * info = (PluginWindowInfo *)_info; GdkVisual * gvis; /* * construct a new toplevel window to draw into. Make sure window is * realised so that it has an underlying XWindow attached to it. */ gtk_window_resize(GTK_WINDOW(info->window), info->width, info->height); gtk_widget_realize(GTK_WIDGET(info->window)); /* initialise imlib */ gvis = gdk_visual_get_system(); imlib_context_set_display(GDK_WINDOW_XDISPLAY(info->window->window)); imlib_context_set_drawable(GDK_WINDOW_XWINDOW(info->window->window)); imlib_context_set_visual(gdk_x11_visual_get_xvisual(gvis)); gtk_widget_show(GTK_WIDGET(info->window)); info->timeout_id = g_timeout_add(40, update_frame, (gpointer)info); g_signal_connect(GTK_WIDGET(info->window), "destroy-event", (GCallback)thread_delete, info); //SEM_SIGNAL(info->start_stop_sem); gtk_init_add (display_signal, info); gtk_main(); close_audio(info); info->shutdown_gui = 0; pthread_exit(NULL); } void * initialise_gui(nsPluginInstance *instance, NPWindow * aWindow, void * ogg_handle) { GtkWidget * plug; PluginWindowInfo * info; plug = gtk_plug_new((Window)aWindow->window); /* allocate structure */ info = malloc(sizeof(PluginWindowInfo)); info->pluginInstance = instance; info->width = aWindow->width; info->height = aWindow->height; info->ogg_handle = ogg_handle; info->shutdown_gui = 0; info->window = (GtkWidget*)plug; info->new_oggplay_handle = NULL; info->audio_opened = FALSE; info->audio_handle = NULL; // set depending on the plugin startup settings info->playback_state = PLAYING; info->playback_target = 0; info->playback_finished = FALSE; info->volume = 1.0; /* * create semaphore to lock replacement of oggplay object */ SEM_CREATE(info->oggplay_replace_sem, 1); /* * create semaphore to use for startup / shutdown synchronisation */ SEM_CREATE(info->start_stop_sem, 1); SEM_WAIT(info->start_stop_sem); /* create gtk thread */ pthread_create((pthread_t*)&(info->thread), NULL, display_thread, info); /* * wait for the thread to start up */ SEM_WAIT(info->start_stop_sem); return info; } /* * not required on the linux platform - window changes are automatically * handled by the GtkPlug object (HOORAY for *decent* GUI libraries!) */ void update_gui_with_new_display_size(void *gui_handle, NPWindow *np_window) {} void update_gui_with_new_oggplay(void *handle, void *oggplay_handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; SEM_WAIT(info->oggplay_replace_sem); if (info->new_oggplay_handle != NULL) { shut_oggplay(info->new_oggplay_handle); } info->new_oggplay_handle = oggplay_handle; SEM_SIGNAL(info->oggplay_replace_sem); } void gui_pause(void *handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; info->playback_state = PAUSED; } void gui_play(void *handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; info->playback_state = PLAYING; } short gui_get_current_state(void *handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; if (info->playback_state == PAUSED) { return 0; } if (info->playback_finished) { return 2; } return 1; } void shut_gui(void *handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; gboolean result; info->shutdown_gui = 1; g_signal_emit_by_name(info->window, "destroy-event", NULL, &result); while (info->shutdown_gui) { oggplay_millisleep(10); } shut_oggplay(info->ogg_handle); SEM_CLOSE(info->start_stop_sem); SEM_CLOSE(info->oggplay_replace_sem); free(info); } void gui_set_volume(void *handle, float volume) { PluginWindowInfo * info = (PluginWindowInfo *)handle; if (volume > 1.0) { volume = 1.0; } if (volume < 0.0) { volume = 0.0; } info->volume = volume; } float gui_get_volume(void *handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; return info->volume; } long gui_get_window_width(void *handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; return info->width; } long gui_get_window_height(void *handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; return info->height; }