diff options
46 files changed, 424 insertions, 358 deletions
diff --git a/addons/skin.confluence/720p/Home.xml b/addons/skin.confluence/720p/Home.xml index 8572e35d09..f4aaf56dba 100644 --- a/addons/skin.confluence/720p/Home.xml +++ b/addons/skin.confluence/720p/Home.xml @@ -967,14 +967,14 @@ <left>0</left> <top>6</top> <width>128</width> - <height>50</height> + <height>63</height> <texture>SideFade.png</texture> </control> <control type="image"> <left>128r</left> <top>6</top> <width>128</width> - <height>60</height> + <height>63</height> <texture flipx="true">SideFade.png</texture> </control> </control> diff --git a/language/English/strings.po b/language/English/strings.po index e6c9a8933b..37ac8d79ce 100755 --- a/language/English/strings.po +++ b/language/English/strings.po @@ -9846,7 +9846,12 @@ msgctxt "#20321" msgid "Scanning albums using %s" msgstr "" -#empty strings from id 20322 to 20323 +#empty string with id 20322 + +#: xbmc/video/windows/GUIWindowVideoNav.cpp +msgctxt "#20323" +msgid "Movie plot" +msgstr "" msgctxt "#20324" msgid "Play part..." diff --git a/lib/ffmpeg/libavcodec/dxva2.h b/lib/ffmpeg/libavcodec/dxva2.h index 881b48dac5..ac39e06917 100644 --- a/lib/ffmpeg/libavcodec/dxva2.h +++ b/lib/ffmpeg/libavcodec/dxva2.h @@ -86,7 +86,6 @@ struct dxva_context { * Private to the FFmpeg AVHWAccel implementation */ unsigned report_id; - unsigned last_slice_count; }; /** diff --git a/lib/ffmpeg/libavcodec/dxva2_mpeg2.c b/lib/ffmpeg/libavcodec/dxva2_mpeg2.c index e967770208..5db7316876 100644 --- a/lib/ffmpeg/libavcodec/dxva2_mpeg2.c +++ b/lib/ffmpeg/libavcodec/dxva2_mpeg2.c @@ -22,13 +22,12 @@ #include "dxva2_internal.h" +#define MAX_SLICES 1024 struct dxva2_picture_context { DXVA_PictureParameters pp; DXVA_QmatrixData qm; unsigned slice_count; - DXVA_SliceInfo *slice; - unsigned int slice_alloc; - + DXVA_SliceInfo slice[MAX_SLICES]; const uint8_t *bitstream; unsigned bitstream_size; }; @@ -220,20 +219,9 @@ static int dxva2_mpeg2_start_frame(AVCodecContext *avctx, fill_quantization_matrices(avctx, ctx, s, &ctx_pic->qm); ctx_pic->slice_count = 0; - ctx_pic->slice = NULL; - ctx_pic->slice_alloc = 0; ctx_pic->bitstream_size = 0; ctx_pic->bitstream = NULL; - if (ctx->last_slice_count > 0) - { - ctx_pic->slice = av_fast_realloc(NULL, - &ctx_pic->slice_alloc, - ctx->last_slice_count * sizeof(DXVA_SliceInfo)); - if (!ctx_pic->slice) - return -1; - } - return 0; } @@ -244,14 +232,9 @@ static int dxva2_mpeg2_decode_slice(AVCodecContext *avctx, struct dxva2_picture_context *ctx_pic = s->current_picture_ptr->f.hwaccel_picture_private; unsigned position; - DXVA_SliceInfo* slice; - slice = av_fast_realloc(ctx_pic->slice, - &ctx_pic->slice_alloc, - (ctx_pic->slice_count + 1) * sizeof(DXVA_SliceInfo)); - if (!slice) + if (ctx_pic->slice_count >= MAX_SLICES) return -1; - ctx_pic->slice = slice; if (!ctx_pic->bitstream) ctx_pic->bitstream = buffer; @@ -268,7 +251,6 @@ static int dxva2_mpeg2_end_frame(AVCodecContext *avctx) struct MpegEncContext *s = avctx->priv_data; struct dxva2_picture_context *ctx_pic = s->current_picture_ptr->f.hwaccel_picture_private; - struct dxva_context *ctx = avctx->hwaccel_context; int ret; if (ctx_pic->slice_count <= 0 || ctx_pic->bitstream_size <= 0) @@ -280,7 +262,6 @@ static int dxva2_mpeg2_end_frame(AVCodecContext *avctx) if (!ret) ff_mpeg_draw_horiz_band(s, 0, avctx->height); - ctx->last_slice_count = ctx_pic->slice_count; return ret; } diff --git a/lib/ffmpeg/patches/0018-dxva-mpeg2-Allocate-slices-array-dynamically-fixes-v.patch b/lib/ffmpeg/patches/0018-dxva-mpeg2-Allocate-slices-array-dynamically-fixes-v.patch deleted file mode 100644 index 87e31c5b70..0000000000 --- a/lib/ffmpeg/patches/0018-dxva-mpeg2-Allocate-slices-array-dynamically-fixes-v.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 40f4c15370f7027dc5422edcb10e8a3b7e58e83d Mon Sep 17 00:00:00 2001 -From: CrystalP <CrystalP@xbmc.org> -Date: Wed, 5 Oct 2011 12:38:30 -0400 -Subject: [PATCH 18/24] dxva-mpeg2 Allocate slices array dynamically - fixes - videos with > 175 slices. They used to result in - images with a black bottom. - -sample on team ftp samples/PR471/too_many_slices.ts - -Inspired by the vaapi code to reallocate the slices array for each new slice. -Could be more efficient if the array could be preserved for all frames and -freed only at the end of the video, but there doesn't seem to be anywhere -appropriate to free the memory at the end. - -Alternative is to allocate the proper size straight away for a new frame, -instead of realloc'ing for each slice. ---- - libavcodec/dxva2_mpeg2.c | 14 +++++++++++--- - 1 file changed, 11 insertions(+), 3 deletions(-) - -diff --git a/libavcodec/dxva2_mpeg2.c b/libavcodec/dxva2_mpeg2.c -index 951305d..8ba83b6 100644 ---- a/libavcodec/dxva2_mpeg2.c -+++ b/libavcodec/dxva2_mpeg2.c -@@ -22,12 +22,12 @@ - - #include "dxva2_internal.h" - --#define MAX_SLICES (SLICE_MAX_START_CODE - SLICE_MIN_START_CODE + 1) - struct dxva2_picture_context { - DXVA_PictureParameters pp; - DXVA_QmatrixData qm; - unsigned slice_count; -- DXVA_SliceInfo slice[MAX_SLICES]; -+ DXVA_SliceInfo *slice; -+ unsigned int slice_alloc; - - const uint8_t *bitstream; - unsigned bitstream_size; -@@ -220,6 +220,8 @@ static int start_frame(AVCodecContext *avctx, - fill_quantization_matrices(avctx, ctx, s, &ctx_pic->qm); - - ctx_pic->slice_count = 0; -+ ctx_pic->slice = NULL; -+ ctx_pic->slice_alloc = 0; - ctx_pic->bitstream_size = 0; - ctx_pic->bitstream = NULL; - return 0; -@@ -232,9 +234,14 @@ static int decode_slice(AVCodecContext *avctx, - struct dxva2_picture_context *ctx_pic = - s->current_picture_ptr->f.hwaccel_picture_private; - unsigned position; -+ DXVA_SliceInfo* slice; - -- if (ctx_pic->slice_count >= MAX_SLICES) -+ slice = av_fast_realloc(ctx_pic->slice, -+ &ctx_pic->slice_alloc, -+ (ctx_pic->slice_count + 1) * sizeof(DXVA_SliceInfo)); -+ if (!slice) - return -1; -+ ctx_pic->slice = slice; - - if (!ctx_pic->bitstream) - ctx_pic->bitstream = buffer; -@@ -258,6 +265,7 @@ static int end_frame(AVCodecContext *avctx) - &ctx_pic->pp, sizeof(ctx_pic->pp), - &ctx_pic->qm, sizeof(ctx_pic->qm), - commit_bitstream_and_slice_buffer); -+ av_freep(ctx_pic->slice); - } - - AVHWAccel ff_mpeg2_dxva2_hwaccel = { --- -1.7.9.4 - diff --git a/lib/ffmpeg/patches/0019-dxva-mpeg2-speed-up-slice-allocation.patch b/lib/ffmpeg/patches/0019-dxva-mpeg2-speed-up-slice-allocation.patch deleted file mode 100644 index 4336c3180f..0000000000 --- a/lib/ffmpeg/patches/0019-dxva-mpeg2-speed-up-slice-allocation.patch +++ /dev/null @@ -1,75 +0,0 @@ -From 681f74b224e16a4df7f8c4e31a9be56975d57e10 Mon Sep 17 00:00:00 2001 -From: CrystalP <CrystalP@xbmc.org> -Date: Mon, 10 Oct 2011 19:42:50 -0400 -Subject: [PATCH 19/24] dxva-mpeg2 speed up slice allocation - -The number of slices is not very likely to change from frame to frame, so -at the beginning of a new frame, allocate memory for the amount of slices of -the previous frame. Saves a lot of reallocation, for some TV capture samples -there are over 200 slices. - -There wasn't anywhere really appropriate to store last_slice_count (needs to -live from first frame to last frame), so this is likely to cause discussion to -merge upstream. -Adding members to dxva_context breaks ABI, which we don't care too much about -since on Windows we don't support external ffmpeg. -dxva mpeg2 code also has access to MpegEncContext, but adding there would -likely break ABI as well. ---- - libavcodec/dxva2.h | 1 + - libavcodec/dxva2_mpeg2.c | 12 ++++++++++++ - 2 files changed, 13 insertions(+) - -diff --git a/libavcodec/dxva2.h b/libavcodec/dxva2.h -index fc99560..16a6994 100644 ---- a/libavcodec/dxva2.h -+++ b/libavcodec/dxva2.h -@@ -66,6 +66,7 @@ struct dxva_context { - * Private to the FFmpeg AVHWAccel implementation - */ - unsigned report_id; -+ unsigned last_slice_count; - }; - - #endif /* AVCODEC_DXVA_H */ -diff --git a/libavcodec/dxva2_mpeg2.c b/libavcodec/dxva2_mpeg2.c -index 8ba83b6..90507f9 100644 ---- a/libavcodec/dxva2_mpeg2.c -+++ b/libavcodec/dxva2_mpeg2.c -@@ -222,6 +222,16 @@ - ctx_pic->slice_count = 0; - ctx_pic->bitstream_size = 0; - ctx_pic->bitstream = NULL; -+ -+ if (ctx->last_slice_count > 0) -+ { -+ ctx_pic->slice = av_fast_realloc(NULL, -+ &ctx_pic->slice_alloc, -+ ctx->last_slice_count * sizeof(DXVA_SliceInfo)); -+ if (!ctx_pic->slice) -+ return -1; -+ } -+ - return 0; - } - -@@ -251,6 +261,7 @@ - struct MpegEncContext *s = avctx->priv_data; - struct dxva2_picture_context *ctx_pic = - s->current_picture_ptr->f.hwaccel_picture_private; -+ struct dxva_context *ctx = avctx->hwaccel_context; - int ret; - - if (ctx_pic->slice_count <= 0 || ctx_pic->bitstream_size <= 0) -@@ -261,6 +272,8 @@ - commit_bitstream_and_slice_buffer); - if (!ret) - ff_mpeg_draw_horiz_band(s, 0, avctx->height); -+ -+ ctx->last_slice_count = ctx_pic->slice_count; - return ret; - } - --- -1.7.9.4 - diff --git a/lib/ffmpeg/patches/0062-backport-dxva2-bump-max-number-of-slices.patch b/lib/ffmpeg/patches/0062-backport-dxva2-bump-max-number-of-slices.patch new file mode 100644 index 0000000000..9d0e5cbc40 --- /dev/null +++ b/lib/ffmpeg/patches/0062-backport-dxva2-bump-max-number-of-slices.patch @@ -0,0 +1,28 @@ +From bceeccc648baf94a02b7b2c53e44bf77a47773ef Mon Sep 17 00:00:00 2001 +From: Rainer Hochecker <fernetmenta@online.de> +Date: Mon, 27 Jan 2014 08:16:13 +0100 +Subject: [PATCH] dxva2: bump maximum number of slieces for mpeg2 + +Suggested by heleppkes on https://trac.ffmpeg.org/ticket/3133 + +Signed-off-by: Michael Niedermayer <michaelni@gmx.at> +--- + libavcodec/dxva2_mpeg2.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/libavcodec/dxva2_mpeg2.c b/libavcodec/dxva2_mpeg2.c +index 1827dd5..e2f6b70 100644 +--- a/libavcodec/dxva2_mpeg2.c ++++ b/libavcodec/dxva2_mpeg2.c +@@ -22,7 +22,7 @@ + + #include "dxva2_internal.h" + +-#define MAX_SLICES (SLICE_MAX_START_CODE - SLICE_MIN_START_CODE + 1) ++#define MAX_SLICES 1024 + struct dxva2_picture_context { + DXVA_PictureParameters pp; + DXVA_QmatrixData qm; +-- +1.8.5.1 + diff --git a/project/Win32BuildSetup/buildpvraddons.bat b/project/Win32BuildSetup/buildpvraddons.bat index 7a4df2ec84..699c24f126 100644 --- a/project/Win32BuildSetup/buildpvraddons.bat +++ b/project/Win32BuildSetup/buildpvraddons.bat @@ -9,7 +9,7 @@ SET DEPS_DIR=..\BuildDependencies SET TMP_DIR=%DEPS_DIR%\tmp SET LIBNAME=xbmc-pvr-addons -SET VERSION=31558d6e0216f9765215bb91354b312cbb2f397c +SET VERSION=af29425b1e171419d72fb3a0475a10f4a862f0b7 SET SOURCE=%LIBNAME% SET GIT_URL=git://github.com/opdenkamp/%LIBNAME%.git SET SOURCE_DIR=%TMP_DIR%\%SOURCE% diff --git a/tools/depends/target/xbmc-pvr-addons/Makefile b/tools/depends/target/xbmc-pvr-addons/Makefile index 72cc7292fc..36128de391 100644 --- a/tools/depends/target/xbmc-pvr-addons/Makefile +++ b/tools/depends/target/xbmc-pvr-addons/Makefile @@ -2,7 +2,7 @@ include ../../Makefile.include #DEPS= ../../Makefile.include Makefile LIBNAME=xbmc-pvr-addons -VERSION=31558d6e0216f9765215bb91354b312cbb2f397c +VERSION=af29425b1e171419d72fb3a0475a10f4a862f0b7 GIT_DIR=$(TARBALLS_LOCATION)/$(LIBNAME).git BASE_URL=git://github.com/opdenkamp/$(LIBNAME).git DYLIB=$(PLATFORM)/addons/pvr.demo/.libs/libpvrdemo-addon.so diff --git a/xbmc/addons/Addon.cpp b/xbmc/addons/Addon.cpp index 981a2c36e3..e20fd43cf4 100644 --- a/xbmc/addons/Addon.cpp +++ b/xbmc/addons/Addon.cpp @@ -315,8 +315,7 @@ bool CAddon::MeetsVersion(const AddonVersion &version) const // if the addon is one of xbmc's extension point definitions (addonid starts with "xbmc.") // and the minversion is "0.0.0" i.e. no <backwards-compatibility> tag has been specified // we need to assume that the current version is not backwards-compatible and therefore check against the actual version - if (StringUtils::StartsWithNoCase(m_props.id, "xbmc.") && - (strlen(m_props.minversion.c_str()) == 0 || StringUtils::EqualsNoCase(m_props.minversion.c_str(), "0.0.0"))) + if (StringUtils::StartsWithNoCase(m_props.id, "xbmc.") && m_props.minversion.empty()) return m_props.version == version; return m_props.minversion <= version && version <= m_props.version; diff --git a/xbmc/addons/AddonDatabase.cpp b/xbmc/addons/AddonDatabase.cpp index 75e80c10fd..524895a244 100644 --- a/xbmc/addons/AddonDatabase.cpp +++ b/xbmc/addons/AddonDatabase.cpp @@ -181,6 +181,39 @@ int CAddonDatabase::AddAddon(const AddonPtr& addon, return -1; } +AddonVersion CAddonDatabase::GetAddonVersion(const std::string &id) +{ + AddonVersion maxversion("0.0.0"); + try + { + if (NULL == m_pDB.get()) return maxversion; + if (NULL == m_pDS2.get()) return maxversion; + + // there may be multiple addons with this id (eg from different repositories) in the database, + // so we want to retrieve the latest version. Order by version won't work as the database + // won't know that 1.10 > 1.2, so grab them all and order outside + CStdString sql = PrepareSQL("select version from addon where addonID='%s'",id.c_str()); + m_pDS2->query(sql.c_str()); + + if (m_pDS2->eof()) + return maxversion; + + while (!m_pDS2->eof()) + { + AddonVersion version(m_pDS2->fv(0).get_asString()); + if (version > maxversion) + maxversion = version; + m_pDS2->next(); + } + return maxversion; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, id.c_str()); + } + return maxversion; +} + bool CAddonDatabase::GetAddon(const CStdString& id, AddonPtr& addon) { try @@ -242,49 +275,56 @@ bool CAddonDatabase::GetRepoForAddon(const CStdString& addonID, CStdString& repo return false; } -bool CAddonDatabase::GetAddon(int id, AddonPtr& addon) +bool CAddonDatabase::GetAddon(int id, AddonPtr &addon) { try { if (NULL == m_pDB.get()) return false; if (NULL == m_pDS2.get()) return false; - CStdString sql = PrepareSQL("select * from addon where id=%i",id); + std::string sql = "SELECT addon.*," + " broken.reason," + " addonextra.key, addonextra.value," + " dependencies.addon, dependencies.version, dependencies.optional" + " FROM addon" + " LEFT JOIN broken" + " ON broken.addonID = addon.addonID" + " LEFT JOIN addonextra" + " ON addonextra.id = addon.id" + " LEFT JOIN dependencies" + " ON dependencies.id = addon.id"; + + sql += PrepareSQL(" WHERE addon.id=%i", id); + m_pDS2->query(sql.c_str()); if (!m_pDS2->eof()) { - AddonProps props(m_pDS2->fv("addonID" ).get_asString(), - TranslateType(m_pDS2->fv("type").get_asString()), - m_pDS2->fv("version").get_asString(), - m_pDS2->fv("minversion").get_asString()); - props.name = m_pDS2->fv("name").get_asString(); - props.summary = m_pDS2->fv("summary").get_asString(); - props.description = m_pDS2->fv("description").get_asString(); - props.changelog = m_pDS2->fv("changelog").get_asString(); - props.path = m_pDS2->fv("path").get_asString(); - props.icon = m_pDS2->fv("icon").get_asString(); - props.fanart = m_pDS2->fv("fanart").get_asString(); - props.author = m_pDS2->fv("author").get_asString(); - props.disclaimer = m_pDS2->fv("disclaimer").get_asString(); - sql = PrepareSQL("select reason from broken where addonID='%s'",props.id.c_str()); - m_pDS2->query(sql.c_str()); - if (!m_pDS2->eof()) - props.broken = m_pDS2->fv(0).get_asString(); - - sql = PrepareSQL("select key,value from addonextra where id=%i", id); - m_pDS2->query(sql.c_str()); - while (!m_pDS2->eof()) - { - props.extrainfo.insert(make_pair(m_pDS2->fv(0).get_asString(), m_pDS2->fv(1).get_asString())); - m_pDS2->next(); - } - - sql = PrepareSQL("select addon,version,optional from dependencies where id=%i", id); - m_pDS2->query(sql.c_str()); - while (!m_pDS2->eof()) + const dbiplus::query_data &data = m_pDS2->get_result_set().records; + const dbiplus::sql_record* const record = data[0]; + AddonProps props(record->at(addon_addonID).get_asString(), + TranslateType(record->at(addon_type).get_asString()), + record->at(addon_version).get_asString(), + record->at(addon_minversion).get_asString()); + props.name = record->at(addon_name).get_asString(); + props.summary = record->at(addon_summary).get_asString(); + props.description = record->at(addon_description).get_asString(); + props.changelog = record->at(addon_changelog).get_asString(); + props.path = record->at(addon_path).get_asString(); + props.icon = record->at(addon_icon).get_asString(); + props.fanart = record->at(addon_fanart).get_asString(); + props.author = record->at(addon_author).get_asString(); + props.disclaimer = record->at(addon_disclaimer).get_asString(); + props.broken = record->at(broken_reason).get_asString(); + + /* while this is a cartesion join and we'll typically get multiple rows, we rely on the fact that + extrainfo and dependencies are maps, so insert() will insert the first instance only */ + for (dbiplus::query_data::const_iterator i = data.begin(); i != data.end(); ++i) { - props.dependencies.insert(make_pair(m_pDS2->fv(0).get_asString(), make_pair(AddonVersion(m_pDS2->fv(1).get_asString()), m_pDS2->fv(2).get_asBool()))); - m_pDS2->next(); + const dbiplus::sql_record* const record = *i; + if (!record->at(addonextra_key).get_asString().empty()) + props.extrainfo.insert(make_pair(record->at(addonextra_key).get_asString(), record->at(addonextra_value).get_asString())); + if (!m_pDS2->fv(dependencies_addon).get_asString().empty()) + props.dependencies.insert(make_pair(record->at(dependencies_addon).get_asString(), make_pair(AddonVersion(record->at(dependencies_version).get_asString()), record->at(dependencies_optional).get_asBool()))); } addon = CAddonMgr::AddonFromProps(props); @@ -310,10 +350,8 @@ bool CAddonDatabase::GetAddons(VECADDONS& addons) m_pDS->query(sql.c_str()); while (!m_pDS->eof()) { - sql = PrepareSQL("select id from addon where addonID='%s' order by version desc",m_pDS->fv(0).get_asString().c_str()); - m_pDS2->query(sql.c_str()); AddonPtr addon; - if (GetAddon(m_pDS2->fv(0).get_asInt(),addon)) + if (GetAddon(m_pDS->fv(0).get_asString(),addon)) addons.push_back(addon); m_pDS->next(); } @@ -519,7 +557,7 @@ bool CAddonDatabase::Search(const CStdString& search, VECADDONS& addons) if (NULL == m_pDS.get()) return false; CStdString strSQL; - strSQL=PrepareSQL("SELECT id FROM addon WHERE name LIKE '%%%s%%' OR summary LIKE '%%%s%%' OR description LIKE '%%%s%%'", search.c_str(), search.c_str(), search.c_str()); + strSQL=PrepareSQL("SELECT addonID FROM addon WHERE name LIKE '%%%s%%' OR summary LIKE '%%%s%%' OR description LIKE '%%%s%%'", search.c_str(), search.c_str(), search.c_str()); CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); if (!m_pDS->query(strSQL.c_str())) return false; @@ -528,7 +566,7 @@ bool CAddonDatabase::Search(const CStdString& search, VECADDONS& addons) while (!m_pDS->eof()) { AddonPtr addon; - GetAddon(m_pDS->fv(0).get_asInt(),addon); + GetAddon(m_pDS->fv(0).get_asString(),addon); if (addon->Type() >= ADDON_UNKNOWN+1 && addon->Type() < ADDON_SCRAPER_LIBRARY) addons.push_back(addon); m_pDS->next(); @@ -629,26 +667,11 @@ bool CAddonDatabase::DisableAddon(const CStdString &addonID, bool disable /* = t bool CAddonDatabase::BreakAddon(const CStdString &addonID, const CStdString& reason) { - try - { - if (NULL == m_pDB.get()) return false; - if (NULL == m_pDS.get()) return false; - - CStdString sql = PrepareSQL("delete from broken where addonID='%s'", addonID.c_str()); - m_pDS->exec(sql); - - if (!reason.empty()) - { // broken - sql = PrepareSQL("insert into broken(id, addonID, reason) values(NULL, '%s', '%s')", addonID.c_str(),reason.c_str()); - m_pDS->exec(sql); - } - return true; - } - catch (...) - { - CLog::Log(LOGERROR, "%s failed on addon '%s'", __FUNCTION__, addonID.c_str()); - } - return false; + if (reason.empty()) + return ExecuteQuery(PrepareSQL("DELETE FROM broken WHERE addonID='%s'", addonID.c_str())); + else + return ExecuteQuery(PrepareSQL("REPLACE INTO broken(addonID, reason) VALUES('%s', '%s')", + addonID.c_str(), reason.c_str())); } bool CAddonDatabase::HasAddon(const CStdString &addonID) @@ -689,24 +712,7 @@ bool CAddonDatabase::IsSystemPVRAddonEnabled(const CStdString &addonID) CStdString CAddonDatabase::IsAddonBroken(const CStdString &addonID) { - try - { - if (NULL == m_pDB.get()) return ""; - if (NULL == m_pDS.get()) return ""; - - CStdString sql = PrepareSQL("select reason from broken where addonID='%s'", addonID.c_str()); - m_pDS->query(sql.c_str()); - CStdString ret; - if (!m_pDS->eof()) - ret = m_pDS->fv(0).get_asString(); - m_pDS->close(); - return ret; - } - catch (...) - { - CLog::Log(LOGERROR, "%s failed on addon %s", __FUNCTION__, addonID.c_str()); - } - return ""; + return GetSingleValue(PrepareSQL("SELECT reason FROM broken WHERE addonID='%s'", addonID.c_str())); } bool CAddonDatabase::HasDisabledAddons() diff --git a/xbmc/addons/AddonDatabase.h b/xbmc/addons/AddonDatabase.h index 76ad91db90..0901a0b73f 100644 --- a/xbmc/addons/AddonDatabase.h +++ b/xbmc/addons/AddonDatabase.h @@ -34,7 +34,9 @@ public: int AddAddon(const ADDON::AddonPtr& item, int idRepo); bool GetAddon(const CStdString& addonID, ADDON::AddonPtr& addon); bool GetAddons(ADDON::VECADDONS& addons); - bool GetAddon(int id, ADDON::AddonPtr& addon); + + /*! \brief grab the (largest) add-on version for an add-on */ + ADDON::AddonVersion GetAddonVersion(const std::string &id); /*! \brief Grab the repository from which a given addon came \param addonID - the id of the addon in question @@ -137,5 +139,33 @@ protected: virtual bool UpdateOldVersion(int version); virtual int GetMinVersion() const { return 16; } const char *GetBaseDBName() const { return "Addons"; } + + bool GetAddon(int id, ADDON::AddonPtr& addon); + + /* keep in sync with the select in GetAddon */ + enum _AddonFields + { + addon_id=0, + addon_type, + addon_name, + addon_summary, + addon_description, + addon_stars, + addon_path, + addon_addonID, + addon_icon, + addon_version, + addon_changelog, + addon_fanart, + addon_author, + addon_disclaimer, + addon_minversion, + broken_reason, + addonextra_key, + addonextra_value, + dependencies_addon, + dependencies_version, + dependencies_optional + } AddonFields; }; diff --git a/xbmc/addons/AddonInstaller.cpp b/xbmc/addons/AddonInstaller.cpp index 73562b4955..ccc8fdfcdf 100644 --- a/xbmc/addons/AddonInstaller.cpp +++ b/xbmc/addons/AddonInstaller.cpp @@ -307,20 +307,22 @@ void CAddonInstaller::InstallFromXBMCRepo(const set<CStdString> &addonIDs) Install(*i); } -bool CAddonInstaller::CheckDependencies(const AddonPtr &addon) +bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, CAddonDatabase *database /* = NULL */) { std::vector<std::string> preDeps; preDeps.push_back(addon->ID()); - return CheckDependencies(addon, preDeps); + CAddonDatabase localDB; + if (!database) + database = &localDB; + return CheckDependencies(addon, preDeps, *database); } bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, - std::vector<std::string>& preDeps) + std::vector<std::string>& preDeps, CAddonDatabase &database) { if (!addon.get()) return true; // a NULL addon has no dependencies ADDONDEPS deps = addon->GetDeps(); - CAddonDatabase database; database.Open(); for (ADDONDEPS::const_iterator i = deps.begin(); i != deps.end(); ++i) { @@ -334,6 +336,7 @@ bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, if (!database.GetAddon(addonID, dep) || !dep->MeetsVersion(version)) { // we don't have it in a repo, or we have it but the version isn't good enough, so dep isn't satisfied. CLog::Log(LOGDEBUG, "Addon %s requires %s version %s which is not available", addon->ID().c_str(), addonID.c_str(), version.c_str()); + database.Close(); return false; } } @@ -341,11 +344,15 @@ bool CAddonInstaller::CheckDependencies(const AddonPtr &addon, // TODO: should we assume that installed deps are OK? if (dep && std::find(preDeps.begin(), preDeps.end(), dep->ID()) == preDeps.end()) { - if (!CheckDependencies(dep, preDeps)) + if (!CheckDependencies(dep, preDeps, database)) + { + database.Close(); return false; + } preDeps.push_back(dep->ID()); } } + database.Close(); return true; } diff --git a/xbmc/addons/AddonInstaller.h b/xbmc/addons/AddonInstaller.h index 150541afc8..eb8ae11db9 100644 --- a/xbmc/addons/AddonInstaller.h +++ b/xbmc/addons/AddonInstaller.h @@ -24,6 +24,8 @@ #include "utils/Stopwatch.h" #include "threads/Event.h" +class CAddonDatabase; + class CAddonInstaller : public IJobCallback { public: @@ -68,9 +70,10 @@ public: Iterates through the addon's dependencies, checking they're installed or installable. Each dependency must also satisfies CheckDependencies in turn. \param addon the addon to check + \param database the database instance to update. Defaults to NULL. \return true if dependencies are available, false otherwise. */ - bool CheckDependencies(const ADDON::AddonPtr &addon); + bool CheckDependencies(const ADDON::AddonPtr &addon, CAddonDatabase *database = NULL); /*! \brief Update all repositories (if needed) Runs through all available repositories and queues an update of them if they @@ -127,10 +130,11 @@ private: Each dependency must also satisfies CheckDependencies in turn. \param addon the addon to check \param preDeps previous dependencies encountered during recursion. aids in avoiding infinite recursion + \param database database instance to update \return true if dependencies are available, false otherwise. */ bool CheckDependencies(const ADDON::AddonPtr &addon, - std::vector<std::string>& preDeps); + std::vector<std::string>& preDeps, CAddonDatabase &database); void PrunePackageCache(); int64_t EnumeratePackageFolder(std::map<CStdString,CFileItemList*>& result); diff --git a/xbmc/addons/AddonVersion.cpp b/xbmc/addons/AddonVersion.cpp index ba22ee1f43..5a6fb4d3ec 100644 --- a/xbmc/addons/AddonVersion.cpp +++ b/xbmc/addons/AddonVersion.cpp @@ -124,6 +124,11 @@ namespace ADDON && CompareComponent(Revision(), other.Revision()) == 0; } + bool AddonVersion::empty() const + { + return m_originalVersion.empty() || m_originalVersion == "0.0.0"; + } + CStdString AddonVersion::Print() const { return StringUtils::Format("%s %s", g_localizeStrings.Get(24051).c_str(), m_originalVersion.c_str()); diff --git a/xbmc/addons/AddonVersion.h b/xbmc/addons/AddonVersion.h index a4ba0f2ad7..f804278169 100644 --- a/xbmc/addons/AddonVersion.h +++ b/xbmc/addons/AddonVersion.h @@ -53,6 +53,7 @@ namespace ADDON bool operator==(const AddonVersion& other) const; CStdString Print() const; const char *c_str() const { return m_originalVersion.c_str(); }; + bool empty() const; static bool SplitFileName(CStdString& ID, CStdString& version, const CStdString& filename); diff --git a/xbmc/addons/Repository.cpp b/xbmc/addons/Repository.cpp index addfb72480..aed7259020 100644 --- a/xbmc/addons/Repository.cpp +++ b/xbmc/addons/Repository.cpp @@ -242,9 +242,11 @@ bool CRepositoryUpdateJob::DoWork() // check for updates CAddonDatabase database; database.Open(); - + database.BeginMultipleExecute(); + CTextureDatabase textureDB; textureDB.Open(); + textureDB.BeginMultipleExecute(); for (map<string, AddonPtr>::const_iterator i = addons.begin(); i != addons.end(); ++i) { // manager told us to feck off @@ -252,7 +254,7 @@ bool CRepositoryUpdateJob::DoWork() break; AddonPtr newAddon = i->second; - bool deps_met = CAddonInstaller::Get().CheckDependencies(newAddon); + bool deps_met = CAddonInstaller::Get().CheckDependencies(newAddon, &database); if (!deps_met && newAddon->Props().broken.empty()) newAddon->Props().broken = "DEPSNOTMET"; @@ -290,12 +292,8 @@ bool CRepositoryUpdateJob::DoWork() // Check if we should mark the add-on as broken. We may have a newer version // of this add-on in the database or installed - if so, we keep it unbroken. - bool haveNewer = addon && addon->Version() > newAddon->Version(); - if (!haveNewer) - { - AddonPtr dbAddon; - haveNewer = database.GetAddon(newAddon->ID(), dbAddon) && dbAddon->Version() > newAddon->Version(); - } + bool haveNewer = (addon && addon->Version() > newAddon->Version()) || + database.GetAddonVersion(newAddon->ID()) > newAddon->Version(); if (!haveNewer) { if (!newAddon->Props().broken.empty()) @@ -315,6 +313,8 @@ bool CRepositoryUpdateJob::DoWork() database.BreakAddon(newAddon->ID(), newAddon->Props().broken); } } + database.CommitMultipleExecute(); + textureDB.CommitMultipleExecute(); return true; } diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp index 14ac2e83cf..da3216ac13 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp @@ -282,7 +282,10 @@ static pa_channel_map AEChannelMapToPAChannel(CAEChannelInfo info) { pos = AEChannelToPAChannel(info[i]); if(pos != PA_CHANNEL_POSITION_INVALID) - map.channels++; + { + // remember channel name and increase channel count + map.map[map.channels++] = pos; + } } return map; } @@ -443,7 +446,10 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) info[0] = pa_format_info_new(); info[0]->encoding = AEFormatToPulseEncoding(format.m_dataFormat); if(!passthrough) + { pa_format_info_set_sample_format(info[0], AEFormatToPulseFormat(format.m_dataFormat)); + pa_format_info_set_channel_map(info[0], &map); + } pa_format_info_set_channels(info[0], m_Channels); // PA requires m_encodedRate in order to do EAC3 @@ -473,7 +479,7 @@ bool CAESinkPULSE::Initialize(AEAudioFormat &format, std::string &device) pa_sample_spec spec; #if PA_CHECK_VERSION(2,0,0) - pa_format_info_to_sample_spec(info[0], &spec, &map); + pa_format_info_to_sample_spec(info[0], &spec, NULL); #else spec.rate = (AEFormatToPulseEncoding(format.m_dataFormat) == PA_ENCODING_EAC3_IEC61937) ? 4 * samplerate : samplerate; spec.format = AEFormatToPulseFormat(format.m_dataFormat); diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPi.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPi.cpp index 72beba4518..d8e28ae085 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPi.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPi.cpp @@ -55,7 +55,7 @@ void CAESinkPi::SetAudioDest() OMX_ERRORTYPE omx_err = OMX_ErrorNone; OMX_CONFIG_BRCMAUDIODESTINATIONTYPE audioDest; OMX_INIT_STRUCTURE(audioDest); - if (CSettings::Get().GetString("audiooutput.audiodevice") == "Pi:Analogue") + if (CSettings::Get().GetString("audiooutput.audiodevice") == "PI:Analogue") strncpy((char *)audioDest.sName, "local", strlen("local")); else strncpy((char *)audioDest.sName, "hdmi", strlen("hdmi")); diff --git a/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp b/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp index 70f75fb329..b70ab916b4 100644 --- a/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp +++ b/xbmc/cores/dvdplayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp @@ -58,6 +58,7 @@ static bool CanSurfaceRenderWhiteList(const std::string &name) "OMX.Nvidia", "OMX.rk", "OMX.qcom", + "OMX.Intel", NULL }; for (const char **ptr = cansurfacerender_decoders; *ptr; ptr++) @@ -916,9 +917,9 @@ void CDVDVideoCodecAndroidMediaCodec::OutputFormatChanged(void) else { // Android device quirks and fixes - if (stride <= 0) + if (stride <= width) stride = width; - if (slice_height <= 0) + if (slice_height <= height) { slice_height = height; if (color_format == CJNIMediaCodecInfoCodecCapabilities::COLOR_FormatYUV420Planar) diff --git a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp index 1f780f5b52..c6674ad84b 100644 --- a/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp +++ b/xbmc/cores/dvdplayer/DVDInputStreams/DVDInputStreamFFmpeg.cpp @@ -19,6 +19,10 @@ */ #include "DVDInputStreamFFmpeg.h" +#include "xbmc/playlists/PlayListM3U.h" +#include "settings/Settings.h" +#include "Util.h" +#include "utils/log.h" using namespace XFILE; @@ -46,6 +50,21 @@ bool CDVDInputStreamFFmpeg::IsEOF() bool CDVDInputStreamFFmpeg::Open(const char* strFile, const std::string& content) { + CFileItem item(strFile, false); + if (item.IsInternetStream() && item.IsType(".m3u8")) + { + // get the available bandwidth and determine the most appropriate stream + int bandwidth = CSettings::Get().GetInt("network.bandwidth"); + if(bandwidth <= 0) + bandwidth = INT_MAX; + std::string selected = PLAYLIST::CPlayListM3U::GetBestBandwidthStream(strFile, bandwidth); + if (selected.compare(strFile) != 0) + { + CLog::Log(LOGINFO, "CDVDInputStreamFFmpeg: Auto-selecting %s based on configured bandwidth.", selected.c_str()); + strFile = selected.c_str(); + } + } + if (!CDVDInputStream::Open(strFile, content)) return false; diff --git a/xbmc/cores/dvdplayer/DVDPlayer.cpp b/xbmc/cores/dvdplayer/DVDPlayer.cpp index 8059f666fa..19ba19f56d 100644 --- a/xbmc/cores/dvdplayer/DVDPlayer.cpp +++ b/xbmc/cores/dvdplayer/DVDPlayer.cpp @@ -662,20 +662,6 @@ bool CDVDPlayer::OpenInputStream() m_filename = g_mediaManager.TranslateDevicePath(""); } - // before creating the input stream, if this is an HLS playlist then get the - // most appropriate bitrate based on our network settings - // ensure to strip off the url options by using a temp CURL object - if (StringUtils::StartsWith(filename, "http://") && - StringUtils::EndsWith(CURL(filename).GetFileName(), ".m3u8")) - { - // get the available bandwidth (as per user settings) - int maxrate = CSettings::Get().GetInt("network.bandwidth"); - if(maxrate <= 0) - maxrate = INT_MAX; - - // determine the most appropriate stream - m_filename = PLAYLIST::CPlayListM3U::GetBestBandwidthStream(m_filename, (size_t)maxrate); - } m_pInputStream = CDVDFactoryInputStream::CreateInputStream(this, m_filename, m_mimetype); if(m_pInputStream == NULL) { diff --git a/xbmc/cores/omxplayer/OMXAudio.cpp b/xbmc/cores/omxplayer/OMXAudio.cpp index 5e5efea339..dd804124dc 100644 --- a/xbmc/cores/omxplayer/OMXAudio.cpp +++ b/xbmc/cores/omxplayer/OMXAudio.cpp @@ -117,12 +117,12 @@ bool COMXAudio::PortSettingsChanged() if(!m_omx_splitter.Initialize("OMX.broadcom.audio_splitter", OMX_IndexParamAudioInit)) return false; } - if (CSettings::Get().GetBool("audiooutput.dualaudio") || CSettings::Get().GetString("audiooutput.audiodevice") == "Pi:Analogue") + if (CSettings::Get().GetBool("audiooutput.dualaudio") || CSettings::Get().GetString("audiooutput.audiodevice") == "PI:Analogue") { if(!m_omx_render_analog.Initialize("OMX.broadcom.audio_render", OMX_IndexParamAudioInit)) return false; } - if (CSettings::Get().GetBool("audiooutput.dualaudio") || CSettings::Get().GetString("audiooutput.audiodevice") != "Pi:Analogue") + if (CSettings::Get().GetBool("audiooutput.dualaudio") || CSettings::Get().GetString("audiooutput.audiodevice") != "PI:Analogue") { if(!m_omx_render_hdmi.Initialize("OMX.broadcom.audio_render", OMX_IndexParamAudioInit)) return false; @@ -470,7 +470,7 @@ bool COMXAudio::Initialize(AEAudioFormat format, OMXClock *clock, CDVDStreamInfo enum PCMChannels outLayout[OMX_AUDIO_MAXCHANNELS]; enum PCMLayout layout = (enum PCMLayout)std::max(0, CSettings::Get().GetInt("audiooutput.channels")-1); // ignore layout setting for analogue - if (CSettings::Get().GetBool("audiooutput.dualaudio") || CSettings::Get().GetString("audiooutput.audiodevice") == "Pi:Analogue") + if (CSettings::Get().GetBool("audiooutput.dualaudio") || CSettings::Get().GetString("audiooutput.audiodevice") == "PI:Analogue") layout = PCM_LAYOUT_2_0; // force out layout to stereo if input is not multichannel - it gives the receiver a chance to upmix diff --git a/xbmc/cores/omxplayer/OMXPlayer.cpp b/xbmc/cores/omxplayer/OMXPlayer.cpp index c039d80f66..a26183ff38 100644 --- a/xbmc/cores/omxplayer/OMXPlayer.cpp +++ b/xbmc/cores/omxplayer/OMXPlayer.cpp @@ -708,19 +708,6 @@ bool COMXPlayer::OpenInputStream() m_filename = g_mediaManager.TranslateDevicePath(""); } - // before creating the input stream, if this is an HLS playlist then get the - // most appropriate bitrate based on our network settings - // ensure to strip off the url options by using a temp CURL object - if (StringUtils::StartsWith(filename, "http://") && StringUtils::EndsWith(CURL(filename).GetFileName(), ".m3u8")) - { - // get the available bandwidth (as per user settings) - int maxrate = CSettings::Get().GetInt("network.bandwidth"); - if(maxrate <= 0) - maxrate = INT_MAX; - - // determine the most appropriate stream - m_filename = PLAYLIST::CPlayListM3U::GetBestBandwidthStream(m_filename, (size_t)maxrate); - } m_pInputStream = CDVDFactoryInputStream::CreateInputStream(this, m_filename, m_mimetype); if(m_pInputStream == NULL) { diff --git a/xbmc/cores/paplayer/CodecFactory.cpp b/xbmc/cores/paplayer/CodecFactory.cpp index 6d3d229c69..995c4be257 100644 --- a/xbmc/cores/paplayer/CodecFactory.cpp +++ b/xbmc/cores/paplayer/CodecFactory.cpp @@ -109,6 +109,8 @@ ICodec* CodecFactory::CreateCodec(const CStdString& strFileType) #endif else if (strFileType.Equals("tta")) return new DVDPlayerCodec(); + else if (strFileType.Equals("tak")) + return new DVDPlayerCodec(); return NULL; } diff --git a/xbmc/cores/paplayer/FLACcodec.cpp b/xbmc/cores/paplayer/FLACcodec.cpp index cea984493b..2c4d93f103 100644 --- a/xbmc/cores/paplayer/FLACcodec.cpp +++ b/xbmc/cores/paplayer/FLACcodec.cpp @@ -208,7 +208,10 @@ FLAC__StreamDecoderReadStatus FLACCodec::DecoderReadCallback(const FLAC__StreamD *bytes=pThis->m_file.Read(buffer, *bytes); - return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + if (*bytes==0) + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + else + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; } FLAC__StreamDecoderSeekStatus FLACCodec::DecoderSeekCallback(const FLAC__StreamDecoder *decoder, FLAC__uint64 absolute_byte_offset, void *client_data) diff --git a/xbmc/cores/paplayer/PAPlayer.cpp b/xbmc/cores/paplayer/PAPlayer.cpp index a98ec57f73..c08f823694 100644 --- a/xbmc/cores/paplayer/PAPlayer.cpp +++ b/xbmc/cores/paplayer/PAPlayer.cpp @@ -520,6 +520,18 @@ bool PAPlayer::CloseFile(bool reopen) /* wait for the thread to terminate */ StopThread(true);//true - wait for end of thread + + // wait for any pending jobs to complete + { + CSharedLock lock(m_streamsLock); + while (m_jobCounter > 0) + { + lock.Leave(); + m_jobEvent.WaitMSec(100); + lock.Enter(); + } + } + return true; } @@ -553,17 +565,6 @@ void PAPlayer::Process() GetTimeInternal(); //update for GUI } - // wait for any pending jobs to complete - { - CSharedLock lock(m_streamsLock); - while (m_jobCounter > 0) - { - lock.Leave(); - m_jobEvent.WaitMSec(100); - lock.Enter(); - } - } - if(m_isFinished && !m_bStop) m_callback.OnPlayBackEnded(); else diff --git a/xbmc/dbwrappers/Database.cpp b/xbmc/dbwrappers/Database.cpp index 8128435412..1deb4c9ee6 100644 --- a/xbmc/dbwrappers/Database.cpp +++ b/xbmc/dbwrappers/Database.cpp @@ -106,6 +106,7 @@ CDatabase::CDatabase(void) m_openCount = 0; m_sqlite = true; m_bMultiWrite = false; + m_multipleExecute = false; } CDatabase::~CDatabase(void) @@ -193,8 +194,35 @@ bool CDatabase::DeleteValues(const CStdString &strTable, const Filter &filter /* return ExecuteQuery(strQuery); } +bool CDatabase::BeginMultipleExecute() +{ + m_multipleExecute = true; + return true; +} + +bool CDatabase::CommitMultipleExecute() +{ + m_multipleExecute = false; + BeginTransaction(); + for (std::vector<std::string>::const_iterator i = m_multipleQueries.begin(); i != m_multipleQueries.end(); ++i) + { + if (!ExecuteQuery(*i)) + { + RollbackTransaction(); + return false; + } + } + return true; +} + bool CDatabase::ExecuteQuery(const CStdString &strQuery) { + if (m_multipleExecute) + { + m_multipleQueries.push_back(strQuery); + return true; + } + bool bReturn = false; try @@ -547,6 +575,7 @@ void CDatabase::Close() } m_openCount = 0; + m_multipleExecute = false; if (NULL == m_pDB.get() ) return ; if (NULL != m_pDS.get()) m_pDS->close(); diff --git a/xbmc/dbwrappers/Database.h b/xbmc/dbwrappers/Database.h index 9dbe4e7e60..a270f0821a 100644 --- a/xbmc/dbwrappers/Database.h +++ b/xbmc/dbwrappers/Database.h @@ -102,8 +102,11 @@ public: /*! * @brief Execute a query that does not return any result. + * Note that if BeginMultipleExecute() has been called, the + * query will be queued until CommitMultipleExecute() is called. * @param strQuery The query to execute. * @return True if the query was executed successfully, false otherwise. + * @sa BeginMultipleExecute, CommitMultipleExecute */ bool ExecuteQuery(const CStdString &strQuery); @@ -116,6 +119,26 @@ public: bool ResultQuery(const CStdString &strQuery); /*! + * @brief Start a multiple execution queue. Any ExecuteQuery() function + * following this call will be queued rather than executed until + * CommitMultipleExecute() is performed. + * NOTE: Queries that rely on any queued execute query will not + * function as expected during this period! + * @return true if we could start a multiple execution queue, false otherwise. + * @sa CommitMultipleExecute, ExecuteQuery + */ + bool BeginMultipleExecute(); + + /*! + * @brief Commit the multiple execution queue to the database. + * Queries are performed within a transaction, and the transaction + * is rolled back should any one query fail. + * @return True if the queries were executed successfully, false otherwise. + * @sa BeginMultipleExecute, ExecuteQuery + */ + bool CommitMultipleExecute(); + + /*! * @brief Open a new dataset. * @return True if the dataset was created successfully, false otherwise. */ @@ -171,4 +194,7 @@ private: bool m_bMultiWrite; /*!< True if there are any queries in the queue, false otherwise */ unsigned int m_openCount; + + bool m_multipleExecute; + std::vector<std::string> m_multipleQueries; }; diff --git a/xbmc/dbwrappers/mysqldataset.cpp b/xbmc/dbwrappers/mysqldataset.cpp index 436f564878..523e621511 100644 --- a/xbmc/dbwrappers/mysqldataset.cpp +++ b/xbmc/dbwrappers/mysqldataset.cpp @@ -155,7 +155,7 @@ int MysqlDatabase::connect(bool create_new) { char sqlcmd[512]; int ret; - sprintf(sqlcmd, "CREATE DATABASE `%s`", db.c_str()); + sprintf(sqlcmd, "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci", db.c_str()); if ( (ret=query_with_reconnect(sqlcmd)) != MYSQL_OK ) { throw DbErrors("Can't create new database: '%s' (%d)", db.c_str(), ret); @@ -250,7 +250,7 @@ int MysqlDatabase::copy(const char *backup_name) { } // create the new database - sprintf(sql, "CREATE DATABASE `%s`", backup_name); + sprintf(sql, "CREATE DATABASE `%s` CHARACTER SET utf8 COLLATE utf8_general_ci", backup_name); if ( (ret=query_with_reconnect(sql)) != MYSQL_OK ) { mysql_free_result(res); @@ -1375,7 +1375,8 @@ int MysqlDataset::exec(const string &sql) { } // force the charset and collation to UTF-8 - if ( ci_find(qry, "CREATE TABLE") != string::npos ) + if ( ci_find(qry, "CREATE TABLE") != string::npos + || ci_find(qry, "CREATE TEMPORARY TABLE") != string::npos ) { qry += " CHARACTER SET utf8 COLLATE utf8_general_ci"; } diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp index 70e12ec583..27966fb5e9 100644 --- a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp +++ b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp @@ -325,7 +325,7 @@ void CGUIDialogSmartPlaylistRule::OnBrowse() } // sort the items - items.Sort(SortByLabel, SortOrderAscending); + items.Sort(SortByLabel, SortOrderAscending, SortAttributeIgnoreArticle); CGUIDialogSelect* pDialog = (CGUIDialogSelect*)g_windowManager.GetWindow(WINDOW_DIALOG_SELECT); pDialog->Reset(); diff --git a/xbmc/guilib/GUIButtonControl.cpp b/xbmc/guilib/GUIButtonControl.cpp index ce6a310940..d59062376a 100644 --- a/xbmc/guilib/GUIButtonControl.cpp +++ b/xbmc/guilib/GUIButtonControl.cpp @@ -137,8 +137,10 @@ void CGUIButtonControl::ProcessText(unsigned int currentTime) m_label2.GetRenderRect() != label2RenderRect); changed |= m_label2.SetColor(GetTextColor()); + changed |= m_label2.Process(currentTime); } changed |= m_label.SetColor(GetTextColor()); + changed |= m_label.Process(currentTime); if (changed) MarkDirtyRegion(); } diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index e144554b06..0f14c1ad41 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -2498,10 +2498,13 @@ bool CMusicDatabase::CleanupArtists() // (nested queries by Bobbin007) // must be executed AFTER the song, album and their artist link tables are cleaned. // don't delete the "Various Artists" string - CStdString strSQL = "delete from artist where idArtist not in (select idArtist from song_artist)"; - strSQL += " and idArtist not in (select idArtist from album_artist)"; - CStdString strSQL2; - m_pDS->exec(strSQL.c_str()); + + // Create temp table to avoid 1442 trigger hell on mysql + m_pDS->exec("CREATE TEMPORARY TABLE tmp_delartists (idArtist integer)"); + m_pDS->exec("INSERT INTO tmp_delartists select idArtist from song_artist"); + m_pDS->exec("INSERT INTO tmp_delartists select idArtist from album_artist"); + m_pDS->exec("delete from artist where idArtist not in (select idArtist from tmp_delartists)"); + m_pDS->exec("DROP TABLE tmp_delartists"); return true; } catch (...) diff --git a/xbmc/network/upnp/UPnPInternal.cpp b/xbmc/network/upnp/UPnPInternal.cpp index 96acecbe28..6841a61430 100644 --- a/xbmc/network/upnp/UPnPInternal.cpp +++ b/xbmc/network/upnp/UPnPInternal.cpp @@ -76,6 +76,22 @@ EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context) } /*---------------------------------------------------------------------- +| GetMediaControllerQuirks ++---------------------------------------------------------------------*/ +EMediaControllerQuirks GetMediaControllerQuirks(const PLT_DeviceData *device) +{ + if (device == NULL) + return EMEDIACONTROLLERQUIRKS_NONE; + + unsigned int quirks = 0; + + if (device->m_Manufacturer.Find("Samsung Electronics") >= 0) + quirks |= EMEDIACONTROLLERQUIRKS_X_MKV; + + return (EMediaControllerQuirks)quirks; +} + +/*---------------------------------------------------------------------- | GetMimeType +---------------------------------------------------------------------*/ NPT_String diff --git a/xbmc/network/upnp/UPnPInternal.h b/xbmc/network/upnp/UPnPInternal.h index a2e758394b..8e9c089d14 100644 --- a/xbmc/network/upnp/UPnPInternal.h +++ b/xbmc/network/upnp/UPnPInternal.h @@ -28,6 +28,7 @@ class CUPnPServer; class CFileItem; class CThumbLoader; +class PLT_DeviceData; class PLT_HttpRequestContext; class PLT_MediaItemResource; class PLT_MediaObject; @@ -64,6 +65,16 @@ namespace UPNP EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context); + enum EMediaControllerQuirks + { + EMEDIACONTROLLERQUIRKS_NONE = 0x00 + + /* Media Controller expects MIME type video/x-mkv instead of video/x-matroska (Samsung) */ + , EMEDIACONTROLLERQUIRKS_X_MKV = 0x01 + }; + + EMediaControllerQuirks GetMediaControllerQuirks(const PLT_DeviceData *device); + const char* GetMimeTypeFromExtension(const char* extension, const PLT_HttpRequestContext* context = NULL); NPT_String GetMimeType(const CFileItem& item, const PLT_HttpRequestContext* context = NULL); NPT_String GetMimeType(const char* filename, const PLT_HttpRequestContext* context = NULL); diff --git a/xbmc/network/upnp/UPnPPlayer.cpp b/xbmc/network/upnp/UPnPPlayer.cpp index 16ed23b9bf..72c7e96c89 100644 --- a/xbmc/network/upnp/UPnPPlayer.cpp +++ b/xbmc/network/upnp/UPnPPlayer.cpp @@ -217,6 +217,7 @@ int CUPnPPlayer::PlayFile(const CFileItem& file, const CPlayerOptions& options, NPT_Reference<PLT_MediaObject> obj; NPT_String path(file.GetPath().c_str()); NPT_String tmp, resource; + EMediaControllerQuirks quirks = EMEDIACONTROLLERQUIRKS_NONE; NPT_CHECK_POINTER_LABEL_SEVERE(m_delegate, failed); @@ -232,12 +233,25 @@ int CUPnPPlayer::PlayFile(const CFileItem& file, const CPlayerOptions& options, tmp.Insert(didl_header, 0); tmp.Append(didl_footer); + quirks = GetMediaControllerQuirks(m_delegate->m_device.AsPointer()); + if (quirks & EMEDIACONTROLLERQUIRKS_X_MKV) + { + for (NPT_Cardinal i=0; i< obj->m_Resources.GetItemCount(); i++) { + if (obj->m_Resources[i].m_ProtocolInfo.GetContentType().Compare("video/x-matroska") == 0) { + NPT_String protocolInfo = obj->m_Resources[i].m_ProtocolInfo.ToString(); + protocolInfo.Replace(":video/x-matroska:", ":video/x-mkv:"); + obj->m_Resources[i].m_ProtocolInfo = PLT_ProtocolInfo(protocolInfo); + } + } + } + /* The resource uri's are stored in the Didl. We must choose the best resource * for the playback device */ NPT_Cardinal res_index; NPT_CHECK_LABEL_SEVERE(m_control->FindBestResource(m_delegate->m_device, *obj, res_index), failed); + timeout.Set(timeout.GetInitialTimeoutValue()); /* dlna specifies that a return code of 705 should be returned * if TRANSPORT_STATE is not STOPPED or NO_MEDIA_PRESENT */ NPT_CHECK_LABEL_SEVERE(m_control->Stop(m_delegate->m_device @@ -247,6 +261,7 @@ int CUPnPPlayer::PlayFile(const CFileItem& file, const CPlayerOptions& options, NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed); + timeout.Set(timeout.GetInitialTimeoutValue()); NPT_CHECK_LABEL_SEVERE(m_control->SetAVTransportURI(m_delegate->m_device , m_delegate->m_instance , obj->m_Resources[res_index].m_Uri @@ -255,6 +270,7 @@ int CUPnPPlayer::PlayFile(const CFileItem& file, const CPlayerOptions& options, NPT_CHECK_LABEL_SEVERE(WaitOnEvent(m_delegate->m_resevent, timeout, dialog), failed); NPT_CHECK_LABEL_SEVERE(m_delegate->m_resstatus, failed); + timeout.Set(timeout.GetInitialTimeoutValue()); NPT_CHECK_LABEL_SEVERE(m_control->Play(m_delegate->m_device , m_delegate->m_instance , "1" @@ -264,6 +280,7 @@ int CUPnPPlayer::PlayFile(const CFileItem& file, const CPlayerOptions& options, /* wait for PLAYING state */ + timeout.Set(timeout.GetInitialTimeoutValue()); do { NPT_CHECK_LABEL_SEVERE(m_control->GetTransportInfo(m_delegate->m_device , m_delegate->m_instance diff --git a/xbmc/network/upnp/UPnPRenderer.cpp b/xbmc/network/upnp/UPnPRenderer.cpp index 767e50694a..3737afff3a 100644 --- a/xbmc/network/upnp/UPnPRenderer.cpp +++ b/xbmc/network/upnp/UPnPRenderer.cpp @@ -157,6 +157,7 @@ CUPnPRenderer::SetupServices() ",http-get:*:video/xvid:*" ",http-get:*:video/x-divx:*" ",http-get:*:video/x-matroska:*" + ",http-get:*:video/x-mkv:*" ",http-get:*:video/x-ms-wmv:*" ",http-get:*:video/x-ms-avi:*" ",http-get:*:video/x-flv:*" diff --git a/xbmc/playlists/PlayListM3U.cpp b/xbmc/playlists/PlayListM3U.cpp index 11d2cd9ee3..6d9d3da918 100644 --- a/xbmc/playlists/PlayListM3U.cpp +++ b/xbmc/playlists/PlayListM3U.cpp @@ -261,10 +261,7 @@ CStdString CPlayListM3U::GetBestBandwidthStream(const CStdString &strFileName, s // if any protocol options were set, restore them subStreamUrl.SetProtocolOptions(playlistUrl.GetProtocolOptions()); - CStdString subStream = subStreamUrl.Get(); - - CLog::Log(LOGINFO, "Auto-selecting %s based on configured bandwidth.", subStream.c_str()); - return subStream; + return subStreamUrl.Get(); } std::map< CStdString, CStdString > CPlayListM3U::ParseStreamLine(const CStdString &streamLine) diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp index 767012e76c..cc98c4359a 100644 --- a/xbmc/pvr/PVRManager.cpp +++ b/xbmc/pvr/PVRManager.cpp @@ -975,6 +975,8 @@ bool CPVRManager::OpenLiveStream(const CFileItem &channel) CDateTime::GetCurrentDateTime().GetAsTime(tNow); playingChannel->SetLastWatched(tNow); bPersistChannel = true; + + m_channelGroups->SetLastPlayedGroup(GetPlayingGroup(playingChannel->IsRadio())); } } @@ -1016,6 +1018,8 @@ void CPVRManager::CloseStream(void) CDateTime::GetCurrentDateTime().GetAsTime(tNow); channel->SetLastWatched(tNow); bPersistChannel = true; + + m_channelGroups->SetLastPlayedGroup(GetPlayingGroup(channel->IsRadio())); } m_addons->CloseStream(); @@ -1218,6 +1222,8 @@ bool CPVRManager::PerformChannelSwitch(const CPVRChannel &channel, bool bPreview time_t tNow; CDateTime::GetCurrentDateTime().GetAsTime(tNow); currentChannel->SetLastWatched(tNow); + + m_channelGroups->SetLastPlayedGroup(GetPlayingGroup(currentChannel->IsRadio())); } // store channel settings diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp index 47a4b2f3e9..3c76d1e731 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.cpp @@ -291,3 +291,16 @@ bool CPVRChannelGroupsContainer::CreateChannelEpgs(void) return m_groupsRadio->CreateChannelEpgs() && m_groupsTV->CreateChannelEpgs(); } + +CPVRChannelGroupPtr CPVRChannelGroupsContainer::GetPreviousPlayedGroup(void) +{ + CSingleLock lock(m_critSection); + return m_lastPlayedGroups[0]; +} + +void CPVRChannelGroupsContainer::SetLastPlayedGroup(CPVRChannelGroupPtr group) +{ + CSingleLock lock(m_critSection); + m_lastPlayedGroups[0] = m_lastPlayedGroups[1]; + m_lastPlayedGroups[1] = group; +} diff --git a/xbmc/pvr/channels/PVRChannelGroupsContainer.h b/xbmc/pvr/channels/PVRChannelGroupsContainer.h index 3edb9ea31b..e17d898e28 100644 --- a/xbmc/pvr/channels/PVRChannelGroupsContainer.h +++ b/xbmc/pvr/channels/PVRChannelGroupsContainer.h @@ -188,6 +188,18 @@ namespace PVR */ bool CreateChannelEpgs(void); + /*! + * @brief Return the group which was previous played. + * @return The group which was previous played. + */ + CPVRChannelGroupPtr GetPreviousPlayedGroup(void); + + /*! + * @brief Set the last played group. + * @param The last played group + */ + void SetLastPlayedGroup(CPVRChannelGroupPtr group); + protected: /*! * @brief Update the contents of all the groups in this container. @@ -201,5 +213,6 @@ namespace PVR CCriticalSection m_critSection; bool m_bUpdateChannelsOnly; bool m_bIsUpdating; + CPVRChannelGroupPtr m_lastPlayedGroups[2]; /*!< used to store the last played groups */ }; } diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp index 6980cc7e0b..462371e599 100644 --- a/xbmc/settings/AdvancedSettings.cpp +++ b/xbmc/settings/AdvancedSettings.cpp @@ -394,7 +394,7 @@ void CAdvancedSettings::Initialize() m_databaseVideo.Reset(); m_pictureExtensions = ".png|.jpg|.jpeg|.bmp|.gif|.ico|.tif|.tiff|.tga|.pcx|.cbz|.zip|.cbr|.rar|.dng|.nef|.cr2|.crw|.orf|.arw|.erf|.3fr|.dcr|.x3f|.mef|.raf|.mrw|.pef|.sr2|.rss"; - m_musicExtensions = ".nsv|.m4a|.flac|.aac|.strm|.pls|.rm|.rma|.mpa|.wav|.wma|.ogg|.mp3|.mp2|.m3u|.mod|.amf|.669|.dmf|.dsm|.far|.gdm|.imf|.it|.m15|.med|.okt|.s3m|.stm|.sfx|.ult|.uni|.xm|.sid|.ac3|.dts|.cue|.aif|.aiff|.wpl|.ape|.mac|.mpc|.mp+|.mpp|.shn|.zip|.rar|.wv|.nsf|.spc|.gym|.adx|.dsp|.adp|.ymf|.ast|.afc|.hps|.xsp|.xwav|.waa|.wvs|.wam|.gcm|.idsp|.mpdsp|.mss|.spt|.rsd|.mid|.kar|.sap|.cmc|.cmr|.dmc|.mpt|.mpd|.rmt|.tmc|.tm8|.tm2|.oga|.url|.pxml|.tta|.rss|.cm3|.cms|.dlt|.brstm|.wtv|.mka"; + m_musicExtensions = ".nsv|.m4a|.flac|.aac|.strm|.pls|.rm|.rma|.mpa|.wav|.wma|.ogg|.mp3|.mp2|.m3u|.mod|.amf|.669|.dmf|.dsm|.far|.gdm|.imf|.it|.m15|.med|.okt|.s3m|.stm|.sfx|.ult|.uni|.xm|.sid|.ac3|.dts|.cue|.aif|.aiff|.wpl|.ape|.mac|.mpc|.mp+|.mpp|.shn|.zip|.rar|.wv|.nsf|.spc|.gym|.adx|.dsp|.adp|.ymf|.ast|.afc|.hps|.xsp|.xwav|.waa|.wvs|.wam|.gcm|.idsp|.mpdsp|.mss|.spt|.rsd|.mid|.kar|.sap|.cmc|.cmr|.dmc|.mpt|.mpd|.rmt|.tmc|.tm8|.tm2|.oga|.url|.pxml|.tta|.rss|.cm3|.cms|.dlt|.brstm|.wtv|.mka|.tak"; m_videoExtensions = ".m4v|.3g2|.3gp|.nsv|.tp|.ts|.ty|.strm|.pls|.rm|.rmvb|.m3u|.m3u8|.ifo|.mov|.qt|.divx|.xvid|.bivx|.vob|.nrg|.img|.iso|.pva|.wmv|.asf|.asx|.ogm|.m2v|.avi|.bin|.dat|.mpg|.mpeg|.mp4|.mkv|.avc|.vp3|.svq3|.nuv|.viv|.dv|.fli|.flv|.rar|.001|.wpl|.zip|.vdr|.dvr-ms|.xsp|.mts|.m2t|.m2ts|.evo|.ogv|.sdp|.avs|.rec|.url|.pxml|.vc1|.h264|.rcv|.rss|.mpls|.webm|.bdmv|.wtv"; m_subtitlesExtensions = ".utf|.utf8|.utf-8|.sub|.srt|.smi|.rt|.txt|.ssa|.text|.ssa|.aqt|.jss|.ass|.idx|.ifo|.rar|.zip"; m_discStubExtensions = ".disc"; diff --git a/xbmc/video/dialogs/GUIDialogSubtitles.cpp b/xbmc/video/dialogs/GUIDialogSubtitles.cpp index d6478afadd..6bf67a8435 100644 --- a/xbmc/video/dialogs/GUIDialogSubtitles.cpp +++ b/xbmc/video/dialogs/GUIDialogSubtitles.cpp @@ -31,6 +31,7 @@ #include "filesystem/SpecialProtocol.h" #include "guilib/GUIImage.h" #include "guilib/GUIKeyboardFactory.h" +#include "guilib/Key.h" #include "settings/MediaSettings.h" #include "settings/Settings.h" #include "settings/VideoSettings.h" @@ -114,7 +115,9 @@ CGUIDialogSubtitles::~CGUIDialogSubtitles(void) bool CGUIDialogSubtitles::OnMessage(CGUIMessage& message) { - if (message.GetMessage() == GUI_MSG_CLICKED) + if (message.GetMessage() == GUI_MSG_CLICKED && + (message.GetParam1() == ACTION_SELECT_ITEM || + message.GetParam1() == ACTION_MOUSE_LEFT_CLICK)) { int iControl = message.GetSenderId(); diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index bca5a63f96..03f1aa638c 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -305,11 +305,14 @@ void CGUIDialogVideoInfo::SetMovie(const CFileItem *item) m_movieItem->m_dateTime = m_movieItem->GetVideoInfoTag()->m_premiered; if(m_movieItem->GetVideoInfoTag()->m_iYear == 0 && m_movieItem->m_dateTime.IsValid()) m_movieItem->GetVideoInfoTag()->m_iYear = m_movieItem->m_dateTime.GetYear(); - m_movieItem->SetProperty("totalepisodes", m_movieItem->GetVideoInfoTag()->m_iEpisode); - m_movieItem->SetProperty("numepisodes", m_movieItem->GetVideoInfoTag()->m_iEpisode); // info view has no concept of current watched/unwatched filter as we could come here from files view, but set for consistency - m_movieItem->SetProperty("watchedepisodes", m_movieItem->GetVideoInfoTag()->m_playCount); - m_movieItem->SetProperty("unwatchedepisodes", m_movieItem->GetVideoInfoTag()->m_iEpisode - m_movieItem->GetVideoInfoTag()->m_playCount); - m_movieItem->GetVideoInfoTag()->m_playCount = (m_movieItem->GetVideoInfoTag()->m_iEpisode == m_movieItem->GetVideoInfoTag()->m_playCount) ? 1 : 0; + if (!m_movieItem->HasProperty("totalepisodes")) + { + m_movieItem->SetProperty("totalepisodes", m_movieItem->GetVideoInfoTag()->m_iEpisode); + m_movieItem->SetProperty("numepisodes", m_movieItem->GetVideoInfoTag()->m_iEpisode); // info view has no concept of current watched/unwatched filter as we could come here from files view, but set for consistency + m_movieItem->SetProperty("watchedepisodes", m_movieItem->GetVideoInfoTag()->m_playCount); + m_movieItem->SetProperty("unwatchedepisodes", m_movieItem->GetVideoInfoTag()->m_iEpisode - m_movieItem->GetVideoInfoTag()->m_playCount); + m_movieItem->GetVideoInfoTag()->m_playCount = (m_movieItem->GetVideoInfoTag()->m_iEpisode == m_movieItem->GetVideoInfoTag()->m_playCount) ? 1 : 0; + } } else if (type == VIDEODB_CONTENT_EPISODES) { diff --git a/xbmc/video/windows/GUIWindowFullScreen.cpp b/xbmc/video/windows/GUIWindowFullScreen.cpp index d51e7f72c7..548d0ff3ea 100644 --- a/xbmc/video/windows/GUIWindowFullScreen.cpp +++ b/xbmc/video/windows/GUIWindowFullScreen.cpp @@ -236,34 +236,40 @@ bool CGUIWindowFullScreen::OnAction(const CAction &action) { if (g_application.CurrentFileItem().IsLiveTV()) { - CPVRChannelPtr channel; - int iChannelNumber = -1; - g_PVRManager.GetCurrentChannel(channel); + CPVRChannelPtr playingChannel; + g_PVRManager.GetCurrentChannel(playingChannel); if (action.GetID() == REMOTE_0) { - iChannelNumber = g_PVRManager.GetPreviousChannel(); - if (iChannelNumber > 0) - CLog::Log(LOGDEBUG, "switch to channel number %d", iChannelNumber); - else - CLog::Log(LOGDEBUG, "no previous channel number found"); + CPVRChannelGroupPtr group = g_PVRChannelGroups->GetPreviousPlayedGroup(); + if (group) + { + g_PVRManager.SetPlayingGroup(group); + CFileItemPtr fileItem = group->GetLastPlayedChannel(playingChannel->ChannelID()); + if (fileItem && fileItem->HasPVRChannelInfoTag()) + { + CLog::Log(LOGDEBUG, "%s - switch to channel number %d", __FUNCTION__, fileItem->GetPVRChannelInfoTag()->ChannelNumber()); + g_application.OnAction(CAction(ACTION_CHANNEL_SWITCH, (float) fileItem->GetPVRChannelInfoTag()->ChannelNumber())); + } + } } else { int autoCloseTime = CSettings::Get().GetBool("pvrplayback.confirmchannelswitch") ? 0 : g_advancedSettings.m_iPVRNumericChannelSwitchTimeout; CStdString strChannel = StringUtils::Format("%i", action.GetID() - REMOTE_0); if (CGUIDialogNumeric::ShowAndGetNumber(strChannel, g_localizeStrings.Get(19000), autoCloseTime) || autoCloseTime) - iChannelNumber = atoi(strChannel.c_str()); - } - - if (iChannelNumber > 0 && iChannelNumber != channel->ChannelNumber()) - { - CPVRChannelGroupPtr selectedGroup = g_PVRManager.GetPlayingGroup(channel->IsRadio()); - CFileItemPtr channel = selectedGroup->GetByChannelNumber(iChannelNumber); - if (!channel || !channel->HasPVRChannelInfoTag()) - return false; + { + int iChannelNumber = atoi(strChannel.c_str()); + if (iChannelNumber > 0 && iChannelNumber != playingChannel->ChannelNumber()) + { + CPVRChannelGroupPtr selectedGroup = g_PVRManager.GetPlayingGroup(playingChannel->IsRadio()); + CFileItemPtr channel = selectedGroup->GetByChannelNumber(iChannelNumber); + if (!channel || !channel->HasPVRChannelInfoTag()) + return false; - g_application.OnAction(CAction(ACTION_CHANNEL_SWITCH, (float)iChannelNumber)); + g_application.OnAction(CAction(ACTION_CHANNEL_SWITCH, (float)iChannelNumber)); + } + } } } else diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index 10bd22a52d..37e01d9cf9 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -600,7 +600,6 @@ void CGUIWindowVideoNav::DoSearch(const CStdString& strSearch, CFileItemList& it CStdString strGenre = g_localizeStrings.Get(515); // Genre CStdString strActor = g_localizeStrings.Get(20337); // Actor CStdString strDirector = g_localizeStrings.Get(20339); // Director - CStdString strMovie = g_localizeStrings.Get(20338); // Movie //get matching names m_database.GetMoviesByName(strSearch, tempItems); @@ -653,7 +652,7 @@ void CGUIWindowVideoNav::DoSearch(const CStdString& strSearch, CFileItemList& it AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20365) + "] ", items); m_database.GetMoviesByPlot(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strMovie + " " + g_localizeStrings.Get(207) + "] ", items); + AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20323) + "] ", items); } void CGUIWindowVideoNav::PlayItem(int iItem) |