$OpenBSD: patch-src_input_input_bluray_c,v 1.2 2012/04/08 19:19:04 ajacoutot Exp $ Add BluRay input plugin. --- src/input/input_bluray.c.orig Sun Apr 8 14:39:52 2012 +++ src/input/input_bluray.c Sun Apr 8 14:39:38 2012 @@ -0,0 +1,1753 @@ +/* + * Copyright (C) 2000-2011 the xine project + * Copyright (C) 2009-2011 Petri Hintukainen + * + * This file is part of xine, a free video player. + * + * xine is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * xine is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA + * + * Input plugin for BluRay discs / images + * + * Requires libbluray 0.2.1 or later: + * http://www.videolan.org/developers/libbluray.html + * git://git.videolan.org/libbluray.git + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include +#include + +/* libbluray */ +#include +#include +#include +#include +#include + +/* xine */ + +#define LOG_MODULE "input_bluray" +#define LOG_VERBOSE + +#define LOG + +#define LOGMSG(x...) xine_log (this->stream->xine, XINE_LOG_MSG, "input_bluray: " x); + +#define XINE_ENGINE_INTERNAL + +#include "xine_internal.h" +#include "xineutils.h" +#include "input_plugin.h" +#include "media_helper.h" + +/* */ + +#ifndef MIN +# define MIN(a,b) ((a)<(b)?(a):(b)) +#endif +#ifndef MAX +# define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +#define ALIGNED_UNIT_SIZE 6144 +#define PKT_SIZE 192 +#define TICKS_IN_MS 45 + +#define MIN_TITLE_LENGTH 180 + +#define BLURAY_MNT_PATH "/mnt/bluray" +#if defined(__sun) +#define BLURAY_PATH "/vol/dev/aliases/cdrom0" +#elif defined(__OpenBSD__) +#define BLURAY_PATH "/dev/rcd0c" +#else +#define BLURAY_PATH "/dev/dvd" +#endif + +/* */ + +typedef struct { + + input_class_t input_class; + + xine_t *xine; + + xine_mrl_t **xine_playlist; + int xine_playlist_size; + + /* config */ + char *mountpoint; + char *device; + char *language; + char *country; + int region; + int parental; +} bluray_input_class_t; + +typedef struct { + input_plugin_t input_plugin; + + bluray_input_class_t *class; + + xine_stream_t *stream; + xine_event_queue_t *event_queue; + xine_osd_t *osd[2]; + + char *mrl; + char *disc_root; + char *disc_name; + + BLURAY *bdh; + + const BLURAY_DISC_INFO *disc_info; + const META_DL *meta_dl; /* disc library meta data */ + + int num_title_idx; /* number of relevant playlists */ + int current_title_idx; + int num_titles; /* navigation mode, number of titles in disc index */ + int current_title; /* navigation mode, title from disc index */ + BLURAY_TITLE_INFO *title_info; + pthread_mutex_t title_info_mutex; /* lock this when accessing title_info outside of input/demux thread */ + unsigned int current_clip; + time_t still_end_time; + int pg_stream; + + uint8_t nav_mode : 1; + uint8_t error : 1; + uint8_t menu_open : 1; + uint8_t stream_flushed : 1; + uint8_t demux_action_req : 1; + uint8_t end_of_title : 1; + uint8_t pg_enable : 1; + int mouse_inside_button; +} bluray_input_plugin_t; + +/* + * overlay + */ + +#define PALETTE_INDEX_BACKGROUND 0xff + +static void send_num_buttons(bluray_input_plugin_t *this, int n) +{ + xine_event_t event; + xine_ui_data_t data; + + event.type = XINE_EVENT_UI_NUM_BUTTONS; + event.data = &data; + event.data_length = sizeof(data); + data.num_buttons = n; + + xine_event_send(this->stream, &event); +} + +static void clear_overlay(xine_osd_t *osd) +{ + /* palette entry 0xff is background --> can't use xine_osd_clear(). */ + memset(osd->osd.area, PALETTE_INDEX_BACKGROUND, osd->osd.width * osd->osd.height); + osd->osd.x1 = osd->osd.width; + osd->osd.y1 = osd->osd.height; + osd->osd.x2 = 0; + osd->osd.y2 = 0; +} + +static xine_osd_t *get_overlay(bluray_input_plugin_t *this, int plane) +{ + if (!this->osd[plane]) { + this->osd[plane] = xine_osd_new(this->stream, 0, 0, 1920, 1080); + clear_overlay(this->osd[plane]); + } + if (!this->pg_enable) { + _x_select_spu_channel(this->stream, -1); + } + return this->osd[plane]; +} + +static void close_overlay(bluray_input_plugin_t *this, int plane) +{ + if (plane < 0) { + close_overlay(this, 0); + close_overlay(this, 1); + return; + } + + if (plane < 2 && this->osd[plane]) { + xine_osd_free(this->osd[plane]); + this->osd[plane] = NULL; + if (plane == 1) { + send_num_buttons(this, 0); + this->menu_open = 0; + } + } +} + +static void open_overlay(bluray_input_plugin_t *this, const BD_OVERLAY * const ov) +{ + lprintf("open_overlay(%d,%d)\n", ov->w, ov->h); + + if (this->osd[ov->plane]) { + close_overlay(this, ov->plane); + } + + this->osd[ov->plane] = xine_osd_new(this->stream, ov->x, ov->y, ov->w, ov->h); + clear_overlay(this->osd[ov->plane]); +} + +static void draw_bitmap(xine_osd_t *osd, const BD_OVERLAY * const ov) +{ + unsigned i; + + /* convert and set palette */ + if (ov->palette) { + uint32_t color[256]; + uint8_t trans[256]; + for(i = 0; i < 256; i++) { + trans[i] = ov->palette[i].T; + color[i] = (ov->palette[i].Y << 16) | (ov->palette[i].Cr << 8) | ov->palette[i].Cb; + } + + xine_osd_set_palette(osd, color, trans); + } + + /* uncompress and draw bitmap */ + if (ov->img) { + const BD_PG_RLE_ELEM *rlep = ov->img; + uint8_t *img = malloc(ov->w * ov->h); + unsigned pixels = ov->w * ov->h; + + for (i = 0; i < pixels; i += rlep->len, rlep++) { + memset(img + i, rlep->color, rlep->len); + } + + xine_osd_draw_bitmap(osd, img, ov->x, ov->y, ov->w, ov->h, NULL); + + free(img); + } +} + +static void overlay_proc(void *this_gen, const BD_OVERLAY * const ov) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + xine_osd_t *osd; + + if (!this) { + return; + } + + if (!ov) { + /* hide OSD */ + close_overlay(this, -1); + return; + } + + if (ov->plane > 1) { + return; + } + + switch (ov->cmd) { + case BD_OVERLAY_INIT: + open_overlay(this, ov); + return; + case BD_OVERLAY_CLOSE: + close_overlay(this, ov->plane); + return; + } + + osd = get_overlay(this, ov->plane); + + switch (ov->cmd) { + case BD_OVERLAY_DRAW: + draw_bitmap(osd, ov); + return; + + case BD_OVERLAY_WIPE: + xine_osd_draw_rect(osd, ov->x, ov->y, ov->x + ov->w - 1, ov->y + ov->h - 1, PALETTE_INDEX_BACKGROUND, 1); + return; + + case BD_OVERLAY_CLEAR: + xine_osd_hide(osd, 0); + clear_overlay(osd); + return; + + case BD_OVERLAY_FLUSH: + xine_osd_show(osd, 0); + + if (ov->plane == 1) { + this->menu_open = 1; + send_num_buttons(this, 1); + } + return; + + default: + lprintf("unknown overlay command %d\n", ov->cmd); + return; + } +} + +/* + * stream info + */ + +static void update_stream_info(bluray_input_plugin_t *this) +{ + if (this->title_info) { + /* set stream info */ + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_COUNT, this->title_info->angle_count); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_NUMBER, bd_get_current_angle(this->bdh)); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_HAS_CHAPTERS, this->title_info->chapter_count > 0); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_CHAPTER_COUNT, this->title_info->chapter_count); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_CHAPTER_NUMBER, bd_get_current_chapter(this->bdh) + 1); + } +} + +static void update_title_name(bluray_input_plugin_t *this) +{ + char title_name[64] = ""; + xine_ui_data_t udata; + xine_event_t uevent = { + .type = XINE_EVENT_UI_SET_TITLE, + .stream = this->stream, + .data = &udata, + .data_length = sizeof(udata) + }; + + /* check disc library metadata */ + if (this->meta_dl) { + unsigned i; + for (i = 0; i < this->meta_dl->toc_count; i++) + if (this->meta_dl->toc_entries[i].title_number == (unsigned)this->current_title) + if (this->meta_dl->toc_entries[i].title_name) + if (strlen(this->meta_dl->toc_entries[i].title_name) > 2) + strncpy(title_name, this->meta_dl->toc_entries[i].title_name, sizeof(title_name)); + } + + /* title name */ + if (title_name[0]) { + } else if (this->current_title == BLURAY_TITLE_TOP_MENU) { + strcpy(title_name, "Top Menu"); + } else if (this->current_title == BLURAY_TITLE_FIRST_PLAY) { + strcpy(title_name, "First Play"); + } else if (this->nav_mode) { + snprintf(title_name, sizeof(title_name), "Title %d/%d (PL %d/%d)", + this->current_title, this->num_titles, + this->current_title_idx + 1, this->num_title_idx); + } else { + snprintf(title_name, sizeof(title_name), "Title %d/%d", + this->current_title_idx + 1, this->num_title_idx); + } + + /* disc name */ + if (this->disc_name && this->disc_name[0]) { + udata.str_len = snprintf(udata.str, sizeof(udata.str), "%s, %s", + this->disc_name, title_name); + } else { + udata.str_len = snprintf(udata.str, sizeof(udata.str), "%s", + title_name); + } + + _x_meta_info_set(this->stream, XINE_META_INFO_TITLE, udata.str); + + xine_event_send(this->stream, &uevent); +} + +static void update_title_info(bluray_input_plugin_t *this, int playlist_id) +{ + /* update title_info */ + + pthread_mutex_lock(&this->title_info_mutex); + + if (this->title_info) + bd_free_title_info(this->title_info); + + if (playlist_id < 0) + this->title_info = bd_get_title_info(this->bdh, this->current_title_idx, 0); + else + this->title_info = bd_get_playlist_info(this->bdh, playlist_id, 0); + + pthread_mutex_unlock(&this->title_info_mutex); + + if (!this->title_info) { + LOGMSG("bd_get_title_info(%d) failed\n", this->current_title_idx); + return; + } + +#ifdef LOG + int ms = this->title_info->duration / INT64_C(90); + lprintf("Opened title %d. Length %"PRId64" bytes / %02d:%02d:%02d.%03d\n", + this->current_title_idx, bd_get_title_size(this->bdh), + ms / 3600000, (ms % 3600000 / 60000), (ms % 60000) / 1000, ms % 1000); +#endif + + /* calculate and set stream rate */ + + uint64_t rate = bd_get_title_size(this->bdh) * UINT64_C(8) // bits + * INT64_C(90000) + / (uint64_t)(this->title_info->duration); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_BITRATE, rate); + + /* set stream info */ + + if (this->nav_mode) { + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_COUNT, this->num_titles); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_NUMBER, this->current_title); + } else { + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_COUNT, this->num_title_idx); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_TITLE_NUMBER, this->current_title_idx + 1); + } + + update_stream_info(this); + + /* set title name */ + + update_title_name(this); +} + +/* + * libbluray event handling + */ + +static void stream_flush(bluray_input_plugin_t *this) +{ + if (this->stream_flushed || !this->stream) + return; + + lprintf("Stream flush\n"); + + this->stream_flushed = 1; + + xine_event_t event = { + .type = XINE_EVENT_END_OF_CLIP, + .stream = this->stream, + .data = NULL, + .data_length = 0, + }; + xine_event_send (this->stream, &event); + + this->demux_action_req = 1; +} + +static void stream_reset(bluray_input_plugin_t *this) +{ + if (!this || !this->stream) + return; + + lprintf("Stream reset\n"); + + xine_event_t event = { + .type = XINE_EVENT_PIDS_CHANGE, + .stream = this->stream, + .data = NULL, + .data_length = 0, + }; + + if (!this->end_of_title) { + _x_demux_flush_engine(this->stream); + } + + xine_event_send (this->stream, &event); + + this->demux_action_req = 1; +} + +static void wait_secs(bluray_input_plugin_t *this, unsigned seconds) +{ + stream_flush(this); + + if (this->still_end_time) { + if (time(NULL) >= this->still_end_time) { + lprintf("pause end\n"); + this->still_end_time = 0; + bd_read_skip_still(this->bdh); + stream_reset(this); + return; + } + } + + else if (seconds) { + if (seconds > 300) { + seconds = 300; + } + + lprintf("still image, pause for %d seconds\n", seconds); + this->still_end_time = time(NULL) + seconds; + } + + xine_usec_sleep(40*1000); +} + +static void update_spu_channel(bluray_input_plugin_t *this, int channel) +{ + if (this->stream->video_fifo) { + buf_element_t *buf = this->stream->video_fifo->buffer_pool_alloc(this->stream->video_fifo); + buf->type = BUF_CONTROL_SPU_CHANNEL; + buf->decoder_info[0] = channel; + buf->decoder_info[1] = channel; + buf->decoder_info[2] = channel; + + this->stream->video_fifo->put(this->stream->video_fifo, buf); + } +} + +static void update_audio_channel(bluray_input_plugin_t *this, int channel) +{ + if (this->stream->audio_fifo) { + buf_element_t *buf = this->stream->audio_fifo->buffer_pool_alloc(this->stream->audio_fifo); + buf->type = BUF_CONTROL_AUDIO_CHANNEL; + buf->decoder_info[0] = channel; + + this->stream->audio_fifo->put(this->stream->audio_fifo, buf); + } +} + +static void handle_libbluray_event(bluray_input_plugin_t *this, BD_EVENT ev) +{ + switch ((bd_event_e)ev.event) { + + case BD_EVENT_NONE: + break; + + case BD_EVENT_ERROR: + lprintf("BD_EVENT_ERROR\n"); + _x_message (this->stream, XINE_MSG_GENERAL_WARNING, + "Error playing BluRay disc", NULL); + this->error = 1; + return; + + case BD_EVENT_READ_ERROR: + LOGMSG("BD_EVENT_READ_ERROR\n"); + return; + + case BD_EVENT_ENCRYPTED: + lprintf("BD_EVENT_ENCRYPTED\n"); + _x_message (this->stream, XINE_MSG_ENCRYPTED_SOURCE, + "Media stream scrambled/encrypted", NULL); + this->error = 1; + return; + + /* sound effects */ +#if BLURAY_VERSION >= 202 + case BD_EVENT_SOUND_EFFECT: + lprintf("BD_EVENT_SOUND_EFFECT %d\n", ev.param); + break; +#endif + + /* playback control */ + + case BD_EVENT_SEEK: + lprintf("BD_EVENT_SEEK\n"); + this->still_end_time = 0; + stream_reset(this); + break; + + case BD_EVENT_STILL_TIME: + wait_secs(this, ev.param); + break; + + case BD_EVENT_STILL: + lprintf("BD_EVENT_STILL %d\n", ev.param); + int paused = _x_get_fine_speed(this->stream) == XINE_SPEED_PAUSE; + if (paused != ev.param) { + _x_set_fine_speed(this->stream, ev.param ? XINE_SPEED_PAUSE : XINE_SPEED_NORMAL); + } + break; + + /* playback position */ + + case BD_EVENT_ANGLE: + lprintf("BD_EVENT_ANGLE_NUMBER %d\n", ev.param); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_NUMBER, ev.param); + break; + + case BD_EVENT_END_OF_TITLE: + lprintf("BD_EVENT_END_OF_TITLE\n"); + stream_flush(this); + this->end_of_title = 1; + break; + + case BD_EVENT_TITLE: + if (this->nav_mode) { + lprintf("BD_EVENT_TITLE %d\n", ev.param); + this->current_title = ev.param; + } + break; + + case BD_EVENT_PLAYLIST: + lprintf("BD_EVENT_PLAYLIST %d\n", ev.param); + this->current_title_idx = bd_get_current_title(this->bdh); + this->current_clip = 0; + update_title_info(this, ev.param); + stream_reset(this); + this->end_of_title = 0; + break; + + case BD_EVENT_PLAYITEM: + lprintf("BD_EVENT_PLAYITEM %d\n", ev.param); + this->current_clip = ev.param; + this->still_end_time = 0; + break; + + case BD_EVENT_CHAPTER: + lprintf("BD_EVENT_CHAPTER %d\n", ev.param); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_CHAPTER_NUMBER, ev.param); + break; + + /* stream selection */ + + case BD_EVENT_AUDIO_STREAM: + lprintf("BD_EVENT_AUDIO_STREAM %d\n", ev.param); + if (ev.param < 32) { + update_audio_channel(this, ev.param - 1); + } else { + update_audio_channel(this, 0); + } + break; + + case BD_EVENT_PG_TEXTST: + lprintf("BD_EVENT_PG_TEXTST %s\n", ev.param ? "ON" : "OFF"); + this->pg_enable = !!ev.param; + update_spu_channel(this, this->pg_enable ? this->pg_stream : -1); + break; + + case BD_EVENT_PG_TEXTST_STREAM: + lprintf("BD_EVENT_PG_TEXTST_STREAM %d\n", ev.param); + if (ev.param < 64) { + this->pg_stream = ev.param - 1; + } else { + this->pg_stream = -1; + } + if (this->pg_enable) { + update_spu_channel(this, this->pg_stream); + } + break; + + default: + lprintf("unhandled libbluray event %d [param %d]\n", ev.event, ev.param); + break; + } +} + +static void handle_libbluray_events(bluray_input_plugin_t *this) +{ + BD_EVENT ev; + while (bd_get_event(this->bdh, &ev)) { + handle_libbluray_event(this, ev); + if (this->error || ev.event == BD_EVENT_NONE || ev.event == BD_EVENT_ERROR) + break; + } +} + +/* + * xine event handling + */ + +static int open_title (bluray_input_plugin_t *this, int title_idx) +{ + if (bd_select_title(this->bdh, title_idx) <= 0) { + LOGMSG("bd_select_title(%d) failed\n", title_idx); + return 0; + } + + this->current_title_idx = title_idx; + + update_title_info(this, -1); + + return 1; +} + +static void send_mouse_enter_leave_event(bluray_input_plugin_t *this, int direction) +{ + if (direction != this->mouse_inside_button) { + xine_event_t event; + xine_spu_button_t spu_event; + + spu_event.direction = direction; + spu_event.button = 1; + + event.type = XINE_EVENT_SPU_BUTTON; + event.stream = this->stream; + event.data = &spu_event; + event.data_length = sizeof(spu_event); + xine_event_send(this->stream, &event); + + this->mouse_inside_button = direction; + } +} + +static void handle_events(bluray_input_plugin_t *this) +{ + xine_event_t *event; + + if (!this->event_queue) + return; + + while (NULL != (event = xine_event_get(this->event_queue))) { + + if (!this->bdh || !this->title_info) { + xine_event_free(event); + return; + } + + int64_t pts = xine_get_current_vpts(this->stream) - + this->stream->metronom->get_option(this->stream->metronom, METRONOM_VPTS_OFFSET); + + if (this->menu_open) { + switch (event->type) { + case XINE_EVENT_INPUT_LEFT: bd_user_input(this->bdh, pts, BD_VK_LEFT); break; + case XINE_EVENT_INPUT_RIGHT: bd_user_input(this->bdh, pts, BD_VK_RIGHT); break; + } + } else { + switch (event->type) { + + case XINE_EVENT_INPUT_LEFT: + lprintf("XINE_EVENT_INPUT_LEFT: previous title\n"); + if (!this->nav_mode) { + open_title(this, MAX(0, this->current_title_idx - 1)); + } else { + bd_play_title(this->bdh, MAX(1, this->current_title - 1)); + } + stream_reset(this); + break; + + case XINE_EVENT_INPUT_RIGHT: + lprintf("XINE_EVENT_INPUT_RIGHT: next title\n"); + if (!this->nav_mode) { + open_title(this, MIN(this->num_title_idx - 1, this->current_title_idx + 1)); + } else { + bd_play_title(this->bdh, MIN(this->num_titles, this->current_title + 1)); + } + stream_reset(this); + break; + } + } + + switch (event->type) { + + case XINE_EVENT_INPUT_MOUSE_BUTTON: { + xine_input_data_t *input = event->data; + lprintf("mouse click: button %d at (%d,%d)\n", input->button, input->x, input->y); + if (input->button == 1) { + bd_mouse_select(this->bdh, pts, input->x, input->y); + bd_user_input(this->bdh, pts, BD_VK_MOUSE_ACTIVATE); + send_mouse_enter_leave_event(this, 0); + } + break; + } + + case XINE_EVENT_INPUT_MOUSE_MOVE: { + xine_input_data_t *input = event->data; + if (bd_mouse_select(this->bdh, pts, input->x, input->y) > 0) { + send_mouse_enter_leave_event(this, 1); + } else { + send_mouse_enter_leave_event(this, 0); + } + break; + } + + case XINE_EVENT_INPUT_MENU1: + if (!this->disc_info->top_menu_supported) { + _x_message (this->stream, XINE_MSG_GENERAL_WARNING, + "Can't open Top Menu", + "Top Menu title not supported", NULL); + } + bd_menu_call(this->bdh, pts); + break; + + case XINE_EVENT_INPUT_MENU2: bd_user_input(this->bdh, pts, BD_VK_POPUP); break; + case XINE_EVENT_INPUT_UP: bd_user_input(this->bdh, pts, BD_VK_UP); break; + case XINE_EVENT_INPUT_DOWN: bd_user_input(this->bdh, pts, BD_VK_DOWN); break; + case XINE_EVENT_INPUT_SELECT: bd_user_input(this->bdh, pts, BD_VK_ENTER); break; + case XINE_EVENT_INPUT_NUMBER_0: bd_user_input(this->bdh, pts, BD_VK_0); break; + case XINE_EVENT_INPUT_NUMBER_1: bd_user_input(this->bdh, pts, BD_VK_1); break; + case XINE_EVENT_INPUT_NUMBER_2: bd_user_input(this->bdh, pts, BD_VK_2); break; + case XINE_EVENT_INPUT_NUMBER_3: bd_user_input(this->bdh, pts, BD_VK_3); break; + case XINE_EVENT_INPUT_NUMBER_4: bd_user_input(this->bdh, pts, BD_VK_4); break; + case XINE_EVENT_INPUT_NUMBER_5: bd_user_input(this->bdh, pts, BD_VK_5); break; + case XINE_EVENT_INPUT_NUMBER_6: bd_user_input(this->bdh, pts, BD_VK_6); break; + case XINE_EVENT_INPUT_NUMBER_7: bd_user_input(this->bdh, pts, BD_VK_7); break; + case XINE_EVENT_INPUT_NUMBER_8: bd_user_input(this->bdh, pts, BD_VK_8); break; + case XINE_EVENT_INPUT_NUMBER_9: bd_user_input(this->bdh, pts, BD_VK_9); break; + + case XINE_EVENT_INPUT_NEXT: { + cfg_entry_t* entry = this->class->xine->config->lookup_entry(this->class->xine->config, + "media.bluray.skip_behaviour"); + switch (entry->num_value) { + case 0: /* skip by chapter */ + bd_seek_chapter(this->bdh, bd_get_current_chapter(this->bdh) + 1); + update_stream_info(this); + break; + case 1: /* skip by title */ + if (!this->nav_mode) { + open_title(this, MIN(this->num_title_idx - 1, this->current_title_idx + 1)); + } else { + bd_play_title(this->bdh, MIN(this->num_titles, this->current_title + 1)); + } + break; + } + stream_reset(this); + break; + } + + case XINE_EVENT_INPUT_PREVIOUS: { + cfg_entry_t* entry = this->class->xine->config->lookup_entry(this->class->xine->config, + "media.bluray.skip_behaviour"); + switch (entry->num_value) { + case 0: /* skip by chapter */ + bd_seek_chapter(this->bdh, MAX(0, ((int)bd_get_current_chapter(this->bdh)) - 1)); + update_stream_info(this); + break; + case 1: /* skip by title */ + if (!this->nav_mode) { + open_title(this, MAX(0, this->current_title_idx - 1)); + } else { + bd_play_title(this->bdh, MAX(1, this->current_title - 1)); + } + break; + } + stream_reset(this); + break; + } + + case XINE_EVENT_INPUT_ANGLE_NEXT: { + unsigned curr_angle = bd_get_current_angle(this->bdh); + unsigned angle = MIN(8, curr_angle + 1); + lprintf("XINE_EVENT_INPUT_ANGLE_NEXT: set angle %d --> %d\n", curr_angle, angle); + bd_seamless_angle_change(this->bdh, angle); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_NUMBER, bd_get_current_angle(this->bdh)); + break; + } + + case XINE_EVENT_INPUT_ANGLE_PREVIOUS: { + unsigned curr_angle = bd_get_current_angle(this->bdh); + unsigned angle = curr_angle ? curr_angle - 1 : 0; + lprintf("XINE_EVENT_INPUT_ANGLE_PREVIOUS: set angle %d --> %d\n", curr_angle, angle); + bd_seamless_angle_change(this->bdh, angle); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_ANGLE_NUMBER, bd_get_current_angle(this->bdh)); + break; + } + } + + xine_event_free(event); + } +} + +/* + * xine plugin interface + */ + +static uint32_t bluray_plugin_get_capabilities (input_plugin_t *this_gen) +{ + return INPUT_CAP_SEEKABLE | + INPUT_CAP_BLOCK | + INPUT_CAP_AUDIOLANG | + INPUT_CAP_SPULANG | + INPUT_CAP_CHAPTERS; +} + +#define CHECK_READ_INTERRUPT \ + do { \ + if (this->demux_action_req) { \ + this->demux_action_req = 0; \ + errno = EAGAIN; \ + return -1; \ + } \ + if (_x_action_pending(this->stream)) { \ + errno = EINTR; \ + return -1; \ + } \ + } while (0) + +static off_t bluray_plugin_read (input_plugin_t *this_gen, char *buf, off_t len) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + off_t result; + + if (!this || !this->bdh || len < 0 || this->error) + return -1; + + handle_events(this); + CHECK_READ_INTERRUPT; + + if (this->nav_mode) { + do { + BD_EVENT ev; + result = bd_read_ext (this->bdh, (unsigned char *)buf, len, &ev); + handle_libbluray_event(this, ev); + CHECK_READ_INTERRUPT; + + if (result == 0) { + handle_events(this); + CHECK_READ_INTERRUPT; +#if 0 + if (ev.event == BD_EVENT_NONE) { + if (_x_action_pending(this->stream)) { + break; + } + } +#endif + } + } while (!this->error && result == 0); + + } else { + result = bd_read (this->bdh, (unsigned char *)buf, len); + handle_libbluray_events(this); + } + + if (result < 0) { + LOGMSG("bd_read() failed: %s (%d of %d)\n", strerror(errno), (int)result, (int)len); + } + + if (result > 0) { + this->stream_flushed = 0; + } + + return result; +} + +static buf_element_t *bluray_plugin_read_block (input_plugin_t *this_gen, fifo_buffer_t *fifo, off_t todo) +{ + buf_element_t *buf = fifo->buffer_pool_alloc (fifo); + + if (todo > (off_t)buf->max_size) + todo = buf->max_size; + + if (todo > ALIGNED_UNIT_SIZE) + todo = ALIGNED_UNIT_SIZE; + + if (todo > 0) { + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + + buf->size = bluray_plugin_read(this_gen, (char*)buf->mem, todo); + buf->type = BUF_DEMUX_BLOCK; + + if (buf->size > 0) { + buf->extra_info->total_time = this->title_info->duration / 90000; + return buf; + } + } + + buf->free_buffer (buf); + return NULL; +} + +static off_t bluray_plugin_seek (input_plugin_t *this_gen, off_t offset, int origin) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + + if (!this || !this->bdh) + return -1; + if (this->still_end_time) + return offset; + + /* convert relative seeks to absolute */ + + if (origin == SEEK_CUR) { + offset = bd_tell(this->bdh) + offset; + } + else if (origin == SEEK_END) { + if (offset < (off_t)bd_get_title_size(this->bdh)) + offset = bd_get_title_size(this->bdh) - offset; + else + offset = 0; + } + + lprintf("bluray_plugin_seek() seeking to %lld\n", (long long)offset); + + return bd_seek (this->bdh, offset); +} + +static off_t bluray_plugin_seek_time (input_plugin_t *this_gen, int time_offset, int origin) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + + if (!this || !this->bdh) + return -1; + + if (this->still_end_time) + return bd_tell(this->bdh); + + /* convert relative seeks to absolute */ + + if (origin == SEEK_CUR) { + time_offset += this_gen->get_current_time(this_gen); + } + else if (origin == SEEK_END) { + + pthread_mutex_lock(&this->title_info_mutex); + + if (!this->title_info) { + pthread_mutex_unlock(&this->title_info_mutex); + return -1; + } + + int duration = this->title_info->duration / 90; + if (time_offset < duration) + time_offset = duration - time_offset; + else + time_offset = 0; + + pthread_mutex_unlock(&this->title_info_mutex); + } + + lprintf("bluray_plugin_seek_time() seeking to %d.%03ds\n", time_offset / 1000, time_offset % 1000); + + return bd_seek_time(this->bdh, time_offset * INT64_C(90)); +} + +static off_t bluray_plugin_get_current_pos (input_plugin_t *this_gen) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + + return this->bdh ? bd_tell(this->bdh) : 0; +} + +static int bluray_plugin_get_current_time (input_plugin_t *this_gen) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + + return this->bdh ? (int)(bd_tell_time(this->bdh) / UINT64_C(90)) : -1; +} + +static off_t bluray_plugin_get_length (input_plugin_t *this_gen) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + + return this->bdh ? (off_t)bd_get_title_size(this->bdh) : (off_t)-1; +} + +static uint32_t bluray_plugin_get_blocksize (input_plugin_t *this_gen) +{ + return ALIGNED_UNIT_SIZE; +} + +static const char* bluray_plugin_get_mrl (input_plugin_t *this_gen) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + + return this->mrl; +} + +static int get_audio_lang (bluray_input_plugin_t *this, int *data) +{ + /* + * audio track language: + * - channel number can be mpeg-ts PID (0x1100 ... 0x11ff) + */ + + unsigned int current_clip = this->current_clip; /* can change any time */ + + if (this->title_info && current_clip < this->title_info->clip_count) { + int channel = *data; + BLURAY_CLIP_INFO *clip = &this->title_info->clips[current_clip]; + + if (channel >= 0 && channel < clip->audio_stream_count) { + memcpy(data, clip->audio_streams[channel].lang, 4); + return INPUT_OPTIONAL_SUCCESS; + } + + /* search by pid */ + int i; + for (i = 0; i < clip->audio_stream_count; i++) { + if (channel == clip->audio_streams[i].pid) { + memcpy(data, clip->audio_streams[i].lang, 4); + return INPUT_OPTIONAL_SUCCESS; + } + } + } + + return INPUT_OPTIONAL_UNSUPPORTED; +} + +static int get_spu_lang (bluray_input_plugin_t *this, int *data) +{ + /* + * SPU track language: + * - channel number can be mpeg-ts PID (0x1200 ... 0x12ff) + */ + + unsigned int current_clip = this->current_clip; /* can change any time */ + + if (this->title_info && current_clip < this->title_info->clip_count) { + int channel = *data; + BLURAY_CLIP_INFO *clip = &this->title_info->clips[current_clip]; + + if (channel >= 0 && channel < clip->pg_stream_count) { + memcpy(data, clip->pg_streams[channel].lang, 4); + return INPUT_OPTIONAL_SUCCESS; + } + + /* search by pid */ + int i; + for (i = 0; i < clip->pg_stream_count; i++) { + if (channel == clip->pg_streams[i].pid) { + memcpy(data, clip->pg_streams[i].lang, 4); + return INPUT_OPTIONAL_SUCCESS; + } + } + } + + return INPUT_OPTIONAL_UNSUPPORTED; +} + +static int get_optional_data_impl (bluray_input_plugin_t *this, void *data, int data_type) +{ + switch (data_type) { + + case INPUT_OPTIONAL_DATA_DEMUXER: + if (data) + *(const char **)data = "mpeg-ts"; + return INPUT_OPTIONAL_SUCCESS; + + case INPUT_OPTIONAL_DATA_AUDIOLANG: + return get_audio_lang(this, data); + + case INPUT_OPTIONAL_DATA_SPULANG: + return get_spu_lang(this, data); + + default: + return INPUT_OPTIONAL_UNSUPPORTED; + } + + return INPUT_OPTIONAL_UNSUPPORTED; +} + +static int bluray_plugin_get_optional_data (input_plugin_t *this_gen, void *data, int data_type) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + int r = INPUT_OPTIONAL_UNSUPPORTED; + + if (this && this->stream && data) { + pthread_mutex_lock(&this->title_info_mutex); + r = get_optional_data_impl(this, data, data_type); + pthread_mutex_unlock(&this->title_info_mutex); + } + + return r; +} + +static void bluray_plugin_dispose (input_plugin_t *this_gen) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + + if (this->bdh) + bd_register_overlay_proc(this->bdh, NULL, NULL); + + close_overlay(this, -1); + + if (this->event_queue) + xine_event_dispose_queue(this->event_queue); + + pthread_mutex_lock(&this->title_info_mutex); + if (this->title_info) + bd_free_title_info(this->title_info); + this->title_info = NULL; + pthread_mutex_unlock(&this->title_info_mutex); + + pthread_mutex_destroy(&this->title_info_mutex); + + if (this->bdh) + bd_close(this->bdh); + + free (this->mrl); + free (this->disc_root); + free (this->disc_name); + + free (this); +} + +static int parse_mrl(const char *mrl_in, char **path, int *title, int *chapter) +{ + int skip = 0; + + if (!strncasecmp(mrl_in, "bluray:", 7)) + skip = 7; + else if (!strncasecmp(mrl_in, "bd:", 3)) + skip = 3; + else + return -1; + + char *mrl = strdup(mrl_in + skip); + + /* title[.chapter] given ? parse and drop it */ + if (mrl[strlen(mrl)-1] != '/') { + char *end = strrchr(mrl, '/'); + if (end && end[1]) { + if (sscanf(end, "/%d.%d", title, chapter) < 1) + *title = -1; + else + *end = 0; + } + } + lprintf(" -> title %d, chapter %d, mrl \'%s\'\n", *title, *chapter, mrl); + + if ((mrl[0] == 0) || !strcmp(mrl, "/") || !strcmp(mrl, "//") || !strcmp(mrl, "///")) { + + /* default device */ + *path = NULL; + + } else if (*mrl == '/') { + + /* strip extra slashes */ + char *start = mrl; + while (start[0] == '/' && start[1] == '/') + start++; + + *path = strdup(start); + + _x_mrl_unescape(*path); + + lprintf("non-defaut mount point \'%s\'\n", *path); + + } else { + lprintf("invalid mrl \'%s\'\n", mrl_in); + free(mrl); + return 0; + } + + free(mrl); + + return 1; +} + +static int get_disc_info(bluray_input_plugin_t *this) +{ + const BLURAY_DISC_INFO *disc_info; + + disc_info = bd_get_disc_info(this->bdh); + + if (!disc_info) { + LOGMSG("bd_get_disc_info() failed\n"); + return -1; + } + + if (!disc_info->bluray_detected) { + LOGMSG("bd_get_disc_info(): BluRay not detected\n"); + this->nav_mode = 0; + return 0; + } + + if (disc_info->aacs_detected && !disc_info->aacs_handled) { + if (!disc_info->libaacs_detected) + _x_message (this->stream, XINE_MSG_ENCRYPTED_SOURCE, + "Media stream scrambled/encrypted with AACS", + "libaacs not installed", NULL); + else + _x_message (this->stream, XINE_MSG_ENCRYPTED_SOURCE, + "Media stream scrambled/encrypted with AACS", NULL); + return -1; + } + + if (disc_info->bdplus_detected && !disc_info->bdplus_handled) { + if (!disc_info->libbdplus_detected) + _x_message (this->stream, XINE_MSG_ENCRYPTED_SOURCE, + "Media scrambled/encrypted with BD+", + "libbdplus not installed.", NULL); + else + _x_message (this->stream, XINE_MSG_ENCRYPTED_SOURCE, + "Media stream scrambled/encrypted with BD+", NULL); + return -1; + } + + if (this->nav_mode && !disc_info->first_play_supported) { + _x_message (this->stream, XINE_MSG_GENERAL_WARNING, + "Can't play disc using menus", + "First Play title not supported", NULL); + this->nav_mode = 0; + } + + if (this->nav_mode && disc_info->num_unsupported_titles > 0) { + _x_message (this->stream, XINE_MSG_GENERAL_WARNING, + "Unsupported titles found", + "Some titles can't be played in navigation mode", NULL); + } + + this->num_titles = disc_info->num_hdmv_titles + disc_info->num_bdj_titles; + this->disc_info = disc_info; + + return 1; +} + +static char *get_disc_name(const char *path) +{ + const char *name_start; + char *file_name = NULL; + int len; + + name_start = path + strlen(path) - 1; + /* skip trailing '/' */ + while (name_start > path && name_start[0] == '/') + name_start--; + /* find prev '/' */ + while (name_start > path && name_start[-1] != '/') + name_start--; + + file_name = strdup(name_start); + len = strlen(file_name); + + /* trim trailing '/' */ + while (len > 0 && file_name[len - 1] == '/') + file_name[--len] = 0; + + /* trim trailing ".iso" */ + if (len > 3 && !strcasecmp(file_name + len - 4, ".iso")) + file_name[len - 4] = 0; + + /* '_' --> ' ' */ + for (len = 0; file_name[len]; ++len) + if (file_name[len] == '_') + file_name[len] = ' '; + + lprintf("disc name: %s\n", file_name); + return file_name; +} + +static int is_iso_image(const char *mrl) +{ + if (mrl) { + const char *pos = strrchr(mrl, '.'); + return pos && !strcasecmp(pos + 1, "iso"); + } + return 0; +} + +static int bluray_plugin_open (input_plugin_t *this_gen) +{ + bluray_input_plugin_t *this = (bluray_input_plugin_t *) this_gen; + int title = -1; + int chapter = 0; + + lprintf("bluray_plugin_open '%s'\n",this->mrl); + + /* validate and parse mrl */ + if (!parse_mrl(this->mrl, &this->disc_root, &title, &chapter)) + return -1; + + if (!strncasecmp(this->mrl, "bd:", 3)) + this->nav_mode = 1; + + if (!this->disc_root) + this->disc_root = strdup(this->class->mountpoint); + + /* mount .iso image */ + if (is_iso_image(this->disc_root)) { + _x_message (this->stream, XINE_MSG_GENERAL_WARNING, + "Can't play BluRay .iso image", + "", NULL); + return -1; + } + + /* open libbluray */ + + if (! (this->bdh = bd_open (this->disc_root, NULL))) { + LOGMSG("bd_open(\'%s\') failed: %s\n", this->disc_root, strerror(errno)); + return -1; + } + lprintf("bd_open(\'%s\') OK\n", this->disc_root); + + if (get_disc_info(this) < 0) { + return -1; + } + + /* load title list */ + + this->num_title_idx = bd_get_titles(this->bdh, TITLES_RELEVANT, MIN_TITLE_LENGTH); + LOGMSG("%d titles\n", this->num_title_idx); + + if (this->num_title_idx < 1) + return -1; + + /* select title */ + + /* if title was not in mrl, guess the main title */ + if (title < 0) { + uint64_t duration = 0; + int i, playlist = 99999; + for (i = 0; i < this->num_title_idx; i++) { + BLURAY_TITLE_INFO *info = bd_get_title_info(this->bdh, i, 0); + if (info->duration > duration) { + title = i; + duration = info->duration; + playlist = info->playlist; + } + bd_free_title_info(info); + } + lprintf("main title: %d (%05d.mpls)\n", title, playlist); + } + + /* update player settings */ + + bd_set_player_setting (this->bdh, BLURAY_PLAYER_SETTING_REGION_CODE, this->class->region); + bd_set_player_setting (this->bdh, BLURAY_PLAYER_SETTING_PARENTAL, this->class->parental); + bd_set_player_setting_str(this->bdh, BLURAY_PLAYER_SETTING_AUDIO_LANG, this->class->language); + bd_set_player_setting_str(this->bdh, BLURAY_PLAYER_SETTING_PG_LANG, this->class->language); + bd_set_player_setting_str(this->bdh, BLURAY_PLAYER_SETTING_MENU_LANG, this->class->language); + bd_set_player_setting_str(this->bdh, BLURAY_PLAYER_SETTING_COUNTRY_CODE, this->class->country); + + /* init event queue */ + bd_get_event(this->bdh, NULL); + + /* get disc name */ + + this->meta_dl = bd_get_meta(this->bdh); + + if (this->meta_dl && this->meta_dl->di_name && strlen(this->meta_dl->di_name) > 1) { + this->disc_name = strdup(this->meta_dl->di_name); + } + else if (strcmp(this->disc_root, this->class->mountpoint)) { + this->disc_name = get_disc_name(this->disc_root); + } + + /* register overlay (graphics) handler */ + + bd_register_overlay_proc(this->bdh, this, overlay_proc); + + /* open */ + this->current_title = -1; + this->current_title_idx = -1; + + if (this->nav_mode) { + if (bd_play(this->bdh) <= 0) { + LOGMSG("bd_play() failed\n"); + return -1; + } + + } else { + if (open_title(this, title) <= 0 && + open_title(this, 0) <= 0) + return -1; + } + + /* jump to chapter */ + + if (chapter > 0) { + chapter = MAX(0, MIN((int)this->title_info->chapter_count, chapter) - 1); + bd_seek_chapter(this->bdh, chapter); + _x_stream_info_set(this->stream, XINE_STREAM_INFO_DVD_CHAPTER_NUMBER, chapter + 1); + } + + return 1; +} + +static input_plugin_t *bluray_class_get_instance (input_class_t *cls_gen, xine_stream_t *stream, + const char *mrl) +{ + bluray_input_plugin_t *this; + + lprintf("bluray_class_get_instance\n"); + + if (strncasecmp(mrl, "bluray:", 7) && strncasecmp(mrl, "bd:", 3)) + return NULL; + + this = (bluray_input_plugin_t *) calloc(1, sizeof (bluray_input_plugin_t)); + + this->stream = stream; + this->class = (bluray_input_class_t*)cls_gen; + this->mrl = strdup(mrl); + + this->input_plugin.open = bluray_plugin_open; + this->input_plugin.get_capabilities = bluray_plugin_get_capabilities; + this->input_plugin.read = bluray_plugin_read; + this->input_plugin.read_block = bluray_plugin_read_block; + this->input_plugin.seek = bluray_plugin_seek; + this->input_plugin.seek_time = bluray_plugin_seek_time; + this->input_plugin.get_current_pos = bluray_plugin_get_current_pos; + this->input_plugin.get_current_time = bluray_plugin_get_current_time; + this->input_plugin.get_length = bluray_plugin_get_length; + this->input_plugin.get_blocksize = bluray_plugin_get_blocksize; + this->input_plugin.get_mrl = bluray_plugin_get_mrl; + this->input_plugin.get_optional_data = bluray_plugin_get_optional_data; + this->input_plugin.dispose = bluray_plugin_dispose; + this->input_plugin.input_class = cls_gen; + + this->event_queue = xine_event_new_queue (this->stream); + + pthread_mutex_init(&this->title_info_mutex, NULL); + + this->pg_stream = -1; + + return &this->input_plugin; +} + +/* + * plugin class + */ + +static void mountpoint_change_cb(void *data, xine_cfg_entry_t *cfg) +{ + bluray_input_class_t *class = (bluray_input_class_t *) data; + + class->mountpoint = cfg->str_value; +} + +static void device_change_cb(void *data, xine_cfg_entry_t *cfg) +{ + bluray_input_class_t *class = (bluray_input_class_t *) data; + + class->device = cfg->str_value; +} + +static void language_change_cb(void *data, xine_cfg_entry_t *cfg) +{ + bluray_input_class_t *class = (bluray_input_class_t *) data; + + class->language = cfg->str_value; +} + +static void country_change_cb(void *data, xine_cfg_entry_t *cfg) +{ + bluray_input_class_t *class = (bluray_input_class_t *) data; + + class->country = cfg->str_value; +} + +static void region_change_cb(void *data, xine_cfg_entry_t *cfg) +{ + bluray_input_class_t *class = (bluray_input_class_t *) data; + + class->region = cfg->num_value; +} + +static void parental_change_cb(void *data, xine_cfg_entry_t *cfg) +{ + bluray_input_class_t *class = (bluray_input_class_t *) data; + + class->parental = cfg->num_value; +} + +static void free_xine_playlist(bluray_input_class_t *this) +{ + if (this->xine_playlist) { + int i; + for (i = 0; i < this->xine_playlist_size; i++) { + MRL_ZERO(this->xine_playlist[i]); + free(this->xine_playlist[i]); + } + free(this->xine_playlist); + this->xine_playlist = NULL; + } + + this->xine_playlist_size = 0; +} + +static const char *bluray_class_get_description (input_class_t *this_gen) +{ + return _("BluRay input plugin"); +} + +static const char *bluray_class_get_identifier (input_class_t *this_gen) +{ + return "bluray"; +} + +static char **bluray_class_get_autoplay_list (input_class_t *this_gen, int *num_files) +{ + static char *autoplay_list[] = { "bluray:/", NULL }; + + *num_files = 1; + + return autoplay_list; +} + +static xine_mrl_t **bluray_class_get_dir(input_class_t *this_gen, const char *filename, int *nFiles) +{ + bluray_input_class_t *this = (bluray_input_class_t*) this_gen; + char *path = NULL; + int title = -1, chapter = -1, i, num_pl; + BLURAY *bdh; + + lprintf("bluray_class_get_dir(%s)\n", filename); + + free_xine_playlist(this); + + if (filename) + parse_mrl(filename, &path, &title, &chapter); + + bdh = bd_open(path ? path : this->mountpoint, NULL); + + if (bdh) { + num_pl = bd_get_titles(bdh, TITLES_RELEVANT, MIN_TITLE_LENGTH); + + if (num_pl > 0) { + + this->xine_playlist_size = num_pl; + this->xine_playlist = calloc(this->xine_playlist_size + 1, sizeof(xine_mrl_t*)); + + for (i = 0; i < num_pl; i++) { + this->xine_playlist[i] = calloc(1, sizeof(xine_mrl_t)); + + this->xine_playlist[i]->origin = _x_asprintf("bluray:/%s", path ? path : ""); + this->xine_playlist[i]->mrl = _x_asprintf("bluray:/%s/%d", path ? path : "", i); + this->xine_playlist[i]->type = mrl_dvd; + } + } + + bd_close(bdh); + } + + free(path); + + if (nFiles) + *nFiles = this->xine_playlist_size; + + return this->xine_playlist; +} + +static int bluray_class_eject_media (input_class_t *this_gen) +{ + bluray_input_class_t *this = (bluray_input_class_t*) this_gen; + + return media_eject_media (this->xine, this->device); +} + +static void bluray_class_dispose (input_class_t *this_gen) +{ + bluray_input_class_t *this = (bluray_input_class_t *) this_gen; + config_values_t *config = this->xine->config; + + free_xine_playlist(this); + + config->unregister_callback(config, "media.bluray.mountpoint"); + config->unregister_callback(config, "media.bluray.device"); + config->unregister_callback(config, "media.bluray.region"); + config->unregister_callback(config, "media.bluray.language"); + config->unregister_callback(config, "media.bluray.country"); + config->unregister_callback(config, "media.bluray.parental"); + + free (this); +} + +static void *bluray_init_plugin (xine_t *xine, void *data) +{ + static const char * const skip_modes[] = {"skip chapter", "skip title", NULL}; + + config_values_t *config = xine->config; + bluray_input_class_t *this = (bluray_input_class_t *) calloc(1, sizeof (bluray_input_class_t)); + + this->xine = xine; + + this->input_class.get_instance = bluray_class_get_instance; + this->input_class.get_identifier = bluray_class_get_identifier; + this->input_class.get_description = bluray_class_get_description; + this->input_class.get_dir = bluray_class_get_dir; + this->input_class.get_autoplay_list = bluray_class_get_autoplay_list; + this->input_class.dispose = bluray_class_dispose; + this->input_class.eject_media = bluray_class_eject_media; + + this->mountpoint = + config->register_filename(config, "media.bluray.mountpoint", + BLURAY_MNT_PATH, XINE_CONFIG_STRING_IS_DIRECTORY_NAME, + _("BluRay mount point"), + _("Default mount location for BluRay discs."), + 0, mountpoint_change_cb, (void *) this); + this->device = + config->register_filename(config, "media.bluray.device", + BLURAY_PATH, XINE_CONFIG_STRING_IS_DIRECTORY_NAME, + _("device used for BluRay playback"), + _("The path to the device " + "which you intend to use for playing BluRy discs."), + 0, device_change_cb, (void *) this); + + /* Player settings */ + this->language = + config->register_string(config, "media.bluray.language", + "eng", + _("default language for BluRay playback"), + _("xine tries to use this language as a default for BluRay playback. " + "As far as the BluRay supports it, menus and audio tracks will be presented " + "in this language.\nThe value must be a three character" + "ISO639-2 language code."), + 0, language_change_cb, this); + this->country = + config->register_string(config, "media.bluray.country", + "en", + _("BluRay player country code"), + _("The value must be a two character ISO3166-1 country code."), + 0, country_change_cb, this); + this->region = + config->register_num(config, "media.bluray.region", + 7, + _("BluRay player region code (1=A, 2=B, 4=C)"), + _("This only needs to be changed if your BluRay jumps to a screen " + "complaining about a wrong region code. It has nothing to do with " + "the region code set in BluRay drives, this is purely software."), + 0, region_change_cb, this); + this->parental = + config->register_num(config, "media.bluray.parental", + 99, + _("parental control age limit (1-99)"), + _("Prevents playback of BluRay titles where parental " + "control age limit is higher than this limit"), + 0, parental_change_cb, this); + + /* */ + config->register_enum(config, "media.bluray.skip_behaviour", 0, + skip_modes, + _("unit for the skip action"), + _("You can configure the behaviour when issuing a skip command (using the skip " + "buttons for example)."), + 20, NULL, NULL); + + return this; +} + +static const char *bd_class_get_description (input_class_t *this_gen) +{ + return _("BluRay input plugin (using menus)"); +} + +static const char *bd_class_get_identifier (input_class_t *this_gen) +{ + return "bd"; +} + +static char **bd_class_get_autoplay_list (input_class_t *this_gen, int *num_files) +{ + static char *autoplay_list[] = { "bd:/", NULL }; + + *num_files = 1; + + return autoplay_list; +} + +static void *bd_init_plugin (xine_t *xine, void *data) +{ + bluray_input_class_t *this = bluray_init_plugin(xine, data); + + if (this) { + this->input_class.get_identifier = bd_class_get_identifier; + this->input_class.get_description = bd_class_get_description; + this->input_class.get_dir = NULL; + this->input_class.get_autoplay_list = bd_class_get_autoplay_list; + } + + return this; +} + +/* + * exported plugin catalog entry + */ + +const plugin_info_t xine_plugin_info[] EXPORTED = { + /* type, API, "name", version, special_info, init_function */ + { PLUGIN_INPUT | PLUGIN_MUST_PRELOAD, INPUT_PLUGIN_IFACE_VERSION, "BLURAY", XINE_VERSION_CODE, NULL, bluray_init_plugin }, + { PLUGIN_INPUT | PLUGIN_MUST_PRELOAD, INPUT_PLUGIN_IFACE_VERSION, "BD", XINE_VERSION_CODE, NULL, bd_init_plugin }, + { PLUGIN_NONE, 0, "", 0, NULL, NULL } +};