/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are 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): Marcin Lubonski * * 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 #include #include "resource.h" #include "std_semaphore.h" #ifdef USE_AUDIO #include "sydney_audio.h" #endif #include "plugin_gui.h" #include "plugin_oggplay.h" #include #define OGGPLAY_FRAME_SKIP_OFFSET -30 #define OGGPLAY_MIN_OFFSET 3 #define SOFT_VOLUME 1 typedef struct { // window related data int width; int height; // video frame PluginOggFrame* frame_data; HWND window; WNDPROC old_wnd_proc; HANDLE thread; int shutdown_gui; void * oggplay_handle; semaphore oggplay_replace_sem; PluginPlaybackState playback_state; __int64 playback_target; void * new_oggplay_handle; #ifdef USE_AUDIO sa_stream_t * audio_handle; BOOL audio_opened; float volume; #endif BOOL set_to_pause; BOOL finished; BOOL valid_frame; nsPluginInstance * plugin_instance; } PluginWindowInfo; #ifdef USE_AUDIO static void open_audio(PluginWindowInfo *info) { info->audio_opened = TRUE; info->playback_target = 0; if (sa_stream_create_pcm(&(info->audio_handle), NULL, SA_MODE_WRONLY, SA_PCM_FORMAT_S16_NE, get_audio_rate(info->oggplay_handle), get_audio_channels(info->oggplay_handle)) != SA_SUCCESS ) { info->audio_opened = FALSE; info->audio_handle = NULL; return; } if (sa_stream_open(info->audio_handle) != SA_SUCCESS) { info->audio_opened = FALSE; info->audio_handle = NULL; return; } printf("Audio device opened at rate [%d] Hz and with [%d] audio channels\n", get_audio_rate(info->oggplay_handle), get_audio_channels(info->oggplay_handle)); return; } static 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"); } } #endif static BOOL switch_oggplays(PluginWindowInfo *info) { BOOL rv = FALSE; void * old_ogg_handle; if (info->new_oggplay_handle != NULL) { SEM_WAIT(info->oggplay_replace_sem); if (info->new_oggplay_handle != NULL) { old_ogg_handle = info->oggplay_handle; info->oggplay_handle = (void *)info->new_oggplay_handle; info->new_oggplay_handle = NULL; #ifdef USE_AUDIO if (info->audio_opened == TRUE) { close_audio(info); } #endif if (info->playback_state == PAUSED) { //info->set_to_pause = TRUE; } rv = TRUE; } SEM_SIGNAL(info->oggplay_replace_sem); shut_oggplay(old_ogg_handle); } return rv; } /*! * \brief initialise_gui() - setup plugin gui and fill in associated structures * \param [in/out] _info - * \param [in] parent - * \param [in] width - window_width * \param [in] height - window_height * Comments: Fuction initialised the display thread * */ void * initialise_gui(nsPluginInstance *instance, NPWindow *np_window, void *oggplay_handle) { PluginWindowInfo * info; HWND hWnd = (HWND)np_window->window; /* allocate structure */ info = (PluginWindowInfo*)malloc(sizeof(PluginWindowInfo)); info->width = np_window->width; info->height = np_window->height; info->oggplay_handle = oggplay_handle; info->frame_data = (PluginOggFrame*)malloc(sizeof(PluginOggFrame)); info->frame_data->frame = NULL; info->frame_data->samples = NULL; info->frame_data->cmml_strings = NULL; info->frame_data->size = 0; info->shutdown_gui = 1; info->window = hWnd; info->new_oggplay_handle = NULL; info->playback_state = PLAYING; /* initialisation of callback related variables */ info->plugin_instance = instance; /* audio and synchronisation related variables */ info->playback_target = 0; info->valid_frame = FALSE; //info->set_to_pause = FALSE; #ifdef USE_AUDIO info->audio_opened = FALSE; info->audio_handle = NULL; info->volume = 1.0; #endif /* * create semaphore to lock replacement of oggplay object */ SEM_CREATE(info->oggplay_replace_sem, 1); // subclass window so we can intercept window messages and // do our drawing to it info->old_wnd_proc = SubclassWindow(hWnd, (WNDPROC)(PluginWinProc)); // associate window with our PliuginWindowInfo so we can access // it in the window procedure SetWindowLongPtr((HWND)hWnd, GWL_USERDATA, (LONG)info); /* * start the main processing loop for the frame data processing * initial timer is called with a default @ 25fps timout */ SetTimer(hWnd, IDT_DISPLAY_TIMER, 40, NULL); return info; } /*! * \brief shut_gui() - clean up and shutdown plugin gui * \param [in/out] handle - pointer to the PluginWindowInfo structure * Comments: Fuction notifies the display * */ void shut_gui(void *handle) { PluginWindowInfo * info = (PluginWindowInfo *)handle; #ifdef USE_AUDIO if (info->audio_opened == TRUE) { close_audio(info); } #endif KillTimer(info->window, IDT_DISPLAY_TIMER); SubclassWindow((HWND)(info->window), (WNDPROC)(info->old_wnd_proc)); SEM_CLOSE(info->oggplay_replace_sem); shut_oggplay(info->oggplay_handle); /*if (info->frame_data != NULL) { free(info->frame_data); }*/ free(info); printf("all closed all done\n"); return; } /* * not required on the windows platform */ void update_gui_with_new_display_size(void *gui_handle, NPWindow *np_window) {} /** * \brief CreateDDBitmap() - create device dependant bitmap * \param [in] hdc - device context * \param [in/out] hBitmap - resulting device dependant GDI bitmap */ static void CreateDDBitmap(HDC hdc, HBITMAP* hBitmap, PluginWindowInfo* info) { BITMAPINFOHEADER bih; BITMAPINFO bmi; // fill in BITMAPINFOHEADER ZeroMemory(&bih, sizeof(BITMAPINFOHEADER)); bih.biClrImportant = 0; bih.biClrUsed = 0; bih.biXPelsPerMeter = 0; bih.biYPelsPerMeter = 0; bih.biSize = 40; bih.biWidth = info->frame_data->width; bih.biHeight = -(info->frame_data->height); // bottom-up bitmap bih.biPlanes = 1; bih.biBitCount = 32; bih.biCompression = BI_RGB; bih.biSizeImage = ((bih.biWidth * 32 + 31) & ~31) /8 * (-bih.biHeight); // fill in BITMAPINFO ZeroMemory(&bmi, sizeof(BITMAPINFO)); bmi.bmiHeader = bih; // create bitmap from raw rgb bits SetDIBits(hdc, (*hBitmap), 0, info->frame_data->height, info->frame_data->frame, &bmi, DIB_RGB_COLORS); return; } static void update_frame(PluginWindowInfo * info) { PAINTSTRUCT ps; RECT r; HDC hdc, hdcMem; HBITMAP hbmOld, hbm; if (info->valid_frame == FALSE) { return; } /* display new video frame */ hdc = BeginPaint(info->window, &ps); GetClientRect(info->window, &r); /* FIXME: very performance costly operations, if safe use global context? */ hdcMem = CreateCompatibleDC(hdc); hbm = CreateCompatibleBitmap(hdc, info->frame_data->width, info->frame_data->height); CreateDDBitmap(hdc, &hbm, info); hbmOld = (HBITMAP)SelectObject(hdcMem, hbm); if ((info->width != info->frame_data->width) || (info->height != info->frame_data->height)) { SetStretchBltMode(hdc, HALFTONE); StretchBlt(hdc, r.left, r.top, info->width, info->height, hdcMem, r.left, r.top, info->frame_data->width, info->frame_data->height, SRCCOPY); } else { BitBlt(hdc, 0, 0, info->width, info->height, hdcMem, 0, 0, SRCCOPY); } //restore the initial state and clean up SelectObject(hdcMem, hbmOld); DeleteObject(hbm); DeleteObject(hdcMem); EndPaint(info->window, &ps); return; } static void process_frame_data(PluginWindowInfo * info) { BOOL has_audio = FALSE; BOOL has_video = FALSE; BOOL has_cmml = FALSE; HWND hWnd; RECT r; __int64 bytes, ref_time, offset; hWnd = (HWND)(info->window); if ((info->playback_state == PAUSED) /*&& (info->set_to_pause == FALSE)*/) { #ifdef USE_AUDIO sa_stream_pause(info->audio_handle); #endif SetTimer(hWnd, IDT_DISPLAY_TIMER, OGGPLAY_MIN_OFFSET, NULL); return; } /* check if we need to change oggplay objects */ switch_oggplays(info); get_oggplay_frame(info->oggplay_handle, info->frame_data); switch (get_oggplay_stream_info(info->oggplay_handle, info->frame_data)) { case OGGPLAY_STREAM_JUST_SEEKED: #ifdef USE_AUDIO close_audio(info); #endif break; case OGGPLAY_STREAM_LAST_DATA: info->finished = TRUE; info->playback_state = FINISHED; #ifdef USE_AUDIO1 close_audio(info); #endif onEndOfMovie(info->plugin_instance); return; default: break; } // this is split so we can skip frames as per the code in the end of that function convert_oggplay_frame(info->oggplay_handle, info->frame_data, BGR); has_video = ((info->frame_data->video_data != NULL) && (info->frame_data->frame != NULL)) ? TRUE : FALSE; has_audio = ((info->frame_data->samples != NULL) && (info->frame_data->size > 0)) ? TRUE : FALSE; has_cmml = ((info->frame_data->cmml_strings != NULL) && (info->frame_data->cmml_size > 0)) ? TRUE : FALSE; /* if there is need to update plugin GUI then go to next frame */ if ((has_video == FALSE) && (has_audio == FALSE) /* && (has_cmml) */ ) { free_oggplay_frame(info->oggplay_handle, info->frame_data); return; } /* we have a valid data to be displayed */ info->valid_frame = TRUE; /* display frame */ GetClientRect(hWnd, &r); InvalidateRect(hWnd, &r, FALSE); UpdateWindow(hWnd); if (info->playback_state == PAUSED) { info->set_to_pause = FALSE; } /* below: audio, timing calculations and frame skipping */ calc_offset: #ifdef USE_AUDIO if (info->audio_opened == FALSE) #endif { info->playback_target = oggplay_sys_time_in_ms() << 16; } #ifdef USE_AUDIO if (has_audio == TRUE) { /* open audio if not opened */ if (info->audio_opened == FALSE) { open_audio(info); } /* if audio device is opened try writing data */ if (info->audio_opened == TRUE) { #ifdef SOFT_VOLUME /* apply per-application volume */ if (info->volume < 1.0) { short * samples = (short *)(info->frame_data->samples); UINT i; for (i = 0; i < info->frame_data->size/sizeof(short); i ++) { samples[i] *= info->volume; } } #endif if (sa_stream_write(info->audio_handle, info->frame_data->samples, info->frame_data->size) != SA_SUCCESS) { close_audio(info); info->playback_target = 0; } } } #endif /* call CMML data callback using plugin's main thread call */ if (has_cmml == TRUE) { onCMMLData(info->plugin_instance, info->frame_data->cmml_strings, info->frame_data->cmml_size, 0); } #ifdef USE_AUDIO 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) { ref_time = oggplay_sys_time_in_ms(); } else { ref_time = (bytes * 1000 / get_audio_rate(info->oggplay_handle) / (sizeof(short) * get_audio_channels(info->oggplay_handle))); } } else #endif { ref_time = oggplay_sys_time_in_ms(); } /* calculate how long we should wait with processing the next frame */ info->playback_target += get_callback_period(info->oggplay_handle); offset = (info->playback_target >> 16) - ref_time; free_oggplay_frame(info->oggplay_handle, info->frame_data); info->valid_frame = FALSE; /* check if we are not lagging with display if (offset < (__int64)OGGPLAY_FRAME_SKIP_OFFSET) { get_oggplay_frame(info->oggplay_handle, info->frame_data); goto calc_offset; } else */ if (offset < (__int64)OGGPLAY_MIN_OFFSET) { offset = OGGPLAY_MIN_OFFSET; } /*printf("setting time for %lld milliseconds (target %lld bytes %lld\n", offset, info->playback_target, bytes);*/ SetTimer(hWnd, IDT_DISPLAY_TIMER, offset, NULL); return; } // ================================= // ! Win32 message processing ! // ! callback procedure ! // ================================= LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { PluginWindowInfo * info = NULL; switch (msg) { case WM_TIMER: info = (PluginWindowInfo*)GetWindowLongPtr(hWnd, GWL_USERDATA); process_frame_data(info); break; case WM_PAINT: info = (PluginWindowInfo*)GetWindowLongPtr(hWnd, GWL_USERDATA); update_frame(info); break; case WM_CLOSE : break; case WM_DESTROY : PostQuitMessage(0); break; case WM_MBUTTONDOWN : //onMouseClick(info->plugin_instance); break; default: break; } return DefWindowProc(hWnd, msg, wParam, lParam); } /* * scripting related code */ 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; #ifdef USE_AUDIO if (info->playback_state == PAUSED) { sa_stream_resume(info->audio_handle); } #endif info->playback_state = PLAYING; } short gui_get_current_state(void *handle) { short state; PluginWindowInfo * info = (PluginWindowInfo *)handle; switch (info->playback_state) { case PAUSED: state = 0; break; case PLAYING: state = 1; break; case FINISHED: state = 2; break; default: /* error state: should never be returned */ state = -1; break; } return state; } void gui_set_volume(void *handle, float volume) { PluginWindowInfo * info; #if USE_AUDIO int vol[1]; info = (PluginWindowInfo *)handle; #ifndef SOFT_VOLUME vol[0] = (int)(volume * 65535); vol[0] = (vol[0] > 65535) ? 65535 : vol[0]; vol[0] = (vol[0] < 0) ? 0 : vol[0]; sa_stream_change_write_volume(info->audio_handle, vol, 1); #else info->volume = volume; #endif #endif return; } float gui_get_volume(void *handle) { PluginWindowInfo * info; float volume; #if USE_AUDIO int vol[1]; #endif volume = 0.0f; #if USE_AUDIO info = (PluginWindowInfo *)handle; #ifndef SOFT_VOLUME if (sa_stream_get_write_volume(info->audio_handle, vol, 1) == SA_SUCCESS) { volume = (vol[0] * 1.0 / 65535); //2 << (sizeof(WORD) * 8) - 1 volume = (volume > 1.0) ? 1.0 : volume; volume = (volume < 0.0) ? 1.0 : volume; } #else volume = info->volume; #endif #endif return volume; } long gui_get_window_width(void *handle) { long width = 0; PluginWindowInfo * info = (PluginWindowInfo *)handle; if (info != NULL) { width = info->width; } return width; } long gui_get_window_height(void *handle) { long height = 0; PluginWindowInfo * info = (PluginWindowInfo *)handle; if (info != NULL) { height = info->height; } return height; }