/*****************************************************************************
* Copyright (C) 2019 VLC authors and VideoLAN
*
* This program 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.
*
* This program 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-1301, USA.
*****************************************************************************/
#if HAVE_CONFIG_H
# include "config.h"
#endif
#include "mlbookmarkmodel.hpp"
#include "qt.hpp"
#include <vlc_cxx_helpers.hpp>
#include "medialib.hpp"
#include "mlhelper.hpp"
#include "util/shared_input_item.hpp"
#include "util/vlctick.hpp"
#include "player/player_controller.hpp"
MLBookmarkModel::MLBookmarkModel( QObject *parent )
: QAbstractListModel( parent )
, m_currentItem( nullptr, &input_item_Release )
, m_ml_event_handle( nullptr, [this](vlc_ml_event_callback_t* cb ) {
if (!cb)
return;
if ( m_mediaLib )
m_mediaLib->unregisterEventListener(cb);
})
{}
MLBookmarkModel::~MLBookmarkModel()
{
if(m_player && m_listener){
vlc_player_locker lock{ m_player };
vlc_player_RemoveListener( m_player, m_listener );
}
}
QVariant MLBookmarkModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() || index.row() < 0 || m_player == nullptr ||
!m_bookmarks || (uint32_t)index.row() >= m_bookmarks->i_nb_items )
return QVariant{};
const auto& bookmark = m_bookmarks->p_items[index.row()];
if (role == Qt::DisplayRole || role == Qt::EditRole)
role = columnToRole(index.column());
switch(role){
case BookmarkRoles::NameRole:
return QVariant::fromValue( qfu( bookmark.psz_name ) );
case BookmarkRoles::TimeRole:
return QVariant::fromValue( VLCDuration::fromMS( bookmark.i_time ).formatHMS() );
case BookmarkRoles::PositionRole:
{
vlc_player_locker lock{ m_player };
return !vlc_player_GetLength( m_player ) ? QVariant{} :
QVariant::fromValue<float> ((float) VLC_TICK_FROM_MS(bookmark.i_time) / vlc_player_GetLength( m_player ));
}
case BookmarkRoles::DescriptionRole:
return QVariant::fromValue( qfu( bookmark.psz_description ) );
default:
return QVariant{};
}
}
bool MLBookmarkModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if ( index.isValid() == false )
return false;
if ( role == Qt::EditRole )
role = columnToRole(index.column());
if ( role != BookmarkRoles::NameRole && role != BookmarkRoles::DescriptionRole )
return false;
if ( value.canConvert<QString>() == false )
return false;
size_t row = index.row();
bool updateName = (index.column() == 0);
if ( ! m_bookmarks || row >= m_bookmarks->i_nb_items )
return false;
auto& b = m_bookmarks->p_items[row];
auto str = value.toString();
struct Ctx {
bool updateSucceed;
};
m_mediaLib->runOnMLThread<Ctx>(this,
//ML thread
[mediaId = b.i_media_id, bookmarkTime = b.i_time, updateName, str]
(vlc_medialibrary_t* ml, Ctx& ctx)
{
int ret;
if ( updateName )
ret = vlc_ml_media_update_bookmark( ml, mediaId, bookmarkTime, qtu( str ), nullptr );
else
ret = vlc_ml_media_update_bookmark( ml, mediaId, bookmarkTime, nullptr, qtu( str ) );
ctx.updateSucceed = (ret == VLC_SUCCESS);
},
//UI thread
[this, updateName, mediaId = m_currentMediaId, row, str]
(quint64, Ctx& ctx)
{
if (!ctx.updateSucceed)
return;
if (m_currentMediaId != mediaId)
return;
auto& b = m_bookmarks->p_items[row];
if (updateName)
{
free( b.psz_name );
b.psz_name = strdup( qtu( str ) );
emit dataChanged(this->index(row, 0), this->index(row, 0),
{Qt::DisplayRole, BookmarkRoles::NameRole});
}
else
{
free( b.psz_description );
b.psz_description = strdup( qtu( str ) );
emit dataChanged(this->index(row, 2), this->index(row, 2),
{Qt::DisplayRole, BookmarkRoles::DescriptionRole});
}
});
return true;
}
QHash<int, QByteArray> MLBookmarkModel::roleNames() const{
return QHash<int, QByteArray>{
{BookmarkRoles::NameRole, QByteArrayLiteral("name")},
{BookmarkRoles::TimeRole, QByteArrayLiteral("time")},
{BookmarkRoles::PositionRole, QByteArrayLiteral("position")},
{BookmarkRoles::DescriptionRole, QByteArrayLiteral("description")}
};
}
int MLBookmarkModel::columnToRole(int column) const{
switch (column)
{
case 0:
return BookmarkRoles::NameRole;
case 1:
return BookmarkRoles::TimeRole;
case 2:
return BookmarkRoles::DescriptionRole;
default:
return -1;
}
}
Qt::ItemFlags MLBookmarkModel::flags( const QModelIndex& index ) const
{
auto f = QAbstractItemModel::flags( index );
if ( index.column() != 1 )
f |= Qt::ItemIsEditable;
return f;
}
int MLBookmarkModel::rowCount(const QModelIndex &) const
{
if ( m_bookmarks == nullptr )
return 0;
return m_bookmarks->i_nb_items;
}
int MLBookmarkModel::columnCount(const QModelIndex &) const
{
return 3;
}
QModelIndex MLBookmarkModel::index( int row, int column, const QModelIndex& ) const
{
return createIndex( row, column, nullptr );
}
QModelIndex MLBookmarkModel::parent(const QModelIndex &) const
{
return createIndex( -1, -1, nullptr );
}
QVariant MLBookmarkModel::headerData( int section, Qt::Orientation orientation,
int role ) const
{
if (role != Qt::DisplayRole || orientation == Qt::Vertical)
return QVariant{};
switch ( section )
{
case 0:
return QVariant::fromValue( qtr( "Name" ) );
case 1:
return QVariant::fromValue( qtr( "Time" ) );
case 2:
return QVariant::fromValue( qtr( "Description" ) );
default:
return QVariant{};
}
}
void MLBookmarkModel::sort( int column, Qt::SortOrder order )
{
switch ( column )
{
case 0:
m_sort = VLC_ML_SORTING_ALPHA;
break;
case 1:
// For bookmarks, default sort order is their time in the media
m_sort = VLC_ML_SORTING_DEFAULT;
break;
case 2:
// We don't support ordering by description, just ignore it
return;
default:
// In all other cases, just list the items as they are inserted
m_sort = VLC_ML_SORTING_INSERTIONDATE;
break;
}
m_desc = order == Qt::DescendingOrder ? true : false;
refresh( MLBOOKMARKMODEL_REFRESH );
}
void MLBookmarkModel::add()
{
vlc_tick_t currentTime;
{
vlc_player_locker lock{ m_player };
currentTime = vlc_player_GetTime( m_player );
}
if (m_currentMediaId == 0)
return;
m_mediaLib->runOnMLThread(this,
//ML thread
[mediaId = m_currentMediaId, currentTime, count = rowCount()](vlc_medialibrary_t* ml)
{
int64_t time = MS_FROM_VLC_TICK(currentTime);
vlc_ml_media_add_bookmark(ml, mediaId, time);
ml_unique_ptr<vlc_ml_media_t> media { vlc_ml_get_media(ml, mediaId) };
if (media)
{
QString name = qtr("Bookmark at %1").arg(VLCDuration::fromMS( time ).formatHMS());
vlc_ml_media_update_bookmark(ml, mediaId, time, qtu(name), nullptr);
}
},
//UI thread
[this](){
refresh( MLBOOKMARKMODEL_REFRESH );
});
}
void MLBookmarkModel::remove( const QModelIndexList &indexes )
{
if (m_currentMediaId == 0)
return;
std::vector<int64_t> bookmarkTimeList;
for ( const auto& i : indexes )
{
if ( i.isValid() == false || (size_t)i.row() >= m_bookmarks->i_nb_items )
continue;
auto& b = m_bookmarks->p_items[i.row()];
bookmarkTimeList.push_back(b.i_time);
}
m_mediaLib->runOnMLThread(this,
//ML thread
[mediaId = m_currentMediaId, bookmarkTimeList]
(vlc_medialibrary_t* ml)
{
for (int64_t bookmarkTime : bookmarkTimeList)
vlc_ml_media_remove_bookmark(ml, mediaId, bookmarkTime);
},
//UI thread
[this](){
refresh( MLBOOKMARKMODEL_REFRESH );
});
}
void MLBookmarkModel::clear()
{
if (m_currentMediaId == 0)
return;
m_mediaLib->runOnMLThread(this,
//ML thread
[mediaId = m_currentMediaId](vlc_medialibrary_t* ml)
{
vlc_ml_media_remove_all_bookmarks( ml, mediaId );
},
//UI thread
[this, mediaId = m_currentMediaId]()
{
if (mediaId == m_currentMediaId)
{
beginResetModel();
m_bookmarks.reset();
endResetModel();
}
});
}
void MLBookmarkModel::select(const QModelIndex &index)
{
if ( index.isValid() == false )
return;
const auto& b = m_bookmarks->p_items[index.row()];
vlc_player_locker lock{ m_player };
vlc_player_SetTime( m_player, VLC_TICK_FROM_MS( b.i_time ) );
}
void MLBookmarkModel::onCurrentMediaChanged( vlc_player_t*, input_item_t* media,
void *data )
{
//Player thread
auto self = static_cast<MLBookmarkModel*>( data );
SharedInputItem item(media, true);
QString mediaUri;
uint64_t revision;
{
vlc::threads::mutex_locker lock{ self->m_mutex };
self->m_currentItem.reset( media ? input_item_Hold( media ) : nullptr );
revision = ++self->m_revision;
if (media)
mediaUri = media->psz_uri;
}
//UI Thread
QMetaObject::invokeMethod(self,
[self, revision, mediaUri]()
{
{
vlc::threads::mutex_locker lock{ self->m_mutex };
//did we start playing a new media in between
if (self->m_revision != revision)
return;
}
self->updateMediaId(revision, mediaUri);
});
}
void MLBookmarkModel::onPlaybackStateChanged( vlc_player_t *, vlc_player_state state,
void *data )
{
auto self = static_cast<MLBookmarkModel*>( data );
if(self->m_currentMediaId == 0)
return;
QMetaObject::invokeMethod(self, [self, state](){
if ( state == VLC_PLAYER_STATE_STARTED )
self->refresh( MLBOOKMARKMODEL_REFRESH );
else if ( state == VLC_PLAYER_STATE_STOPPING )
self->refresh( MLBOOKMARKMODEL_CLEAR );
});
}
void MLBookmarkModel::updateMediaId(uint64_t revision, const QString &mediaUri)
{
if (mediaUri.isEmpty())
{
refresh(MLBOOKMARKMODEL_CLEAR);
return;
}
//retrieve the media id in the medialib
struct Ctx{
uint64_t newMLid = 0;
BookmarkListPtr newBookmarks;
};
m_mediaLib->runOnMLThread<Ctx>(this,
//ML thread
[mediaUri, sort = m_sort, desc = m_desc](vlc_medialibrary_t* ml, Ctx& ctx){
auto mlMedia = vlc_ml_get_media_by_mrl( ml, qtu(mediaUri) );
if ( mlMedia != nullptr )
{
ctx.newMLid = mlMedia->i_id;
vlc_ml_release( mlMedia );
}
vlc_ml_query_params_t params = vlc_ml_query_params_create();
params.i_sort = sort;
params.b_desc = desc;
ctx.newBookmarks.reset( vlc_ml_list_media_bookmarks( ml, ¶ms, ctx.newMLid ) );
},
//UI thread
[this, revision](quint64, Ctx& ctx) {
bool valid;
{
vlc::threads::mutex_locker lock{ m_mutex };
//did we start playing a new media in between
valid = (m_revision == revision);
}
if (valid)
{
beginResetModel();
m_bookmarks = std::move(ctx.newBookmarks);
m_currentMediaId = ctx.newMLid;
refresh(MLBOOKMARKMODEL_REFRESH);
endResetModel();
}
});
}
void MLBookmarkModel::refresh(MLBookmarkModel::RefreshOperation forceClear )
{
if (m_currentMediaId == 0 || forceClear == MLBOOKMARKMODEL_CLEAR)
{
beginResetModel();
m_bookmarks.reset();
endResetModel();
}
else
{
uint64_t mediaId = m_currentMediaId;
struct Ctx
{
BookmarkListPtr newBookmarks;
};
m_mediaLib->runOnMLThread<Ctx>(this,
//ML thread
[mediaId, sort = m_sort, desc = m_desc]
(vlc_medialibrary_t* ml, Ctx& ctx) {
vlc_ml_query_params_t params = vlc_ml_query_params_create();
params.i_sort = sort;
params.b_desc = desc;
ctx.newBookmarks.reset( vlc_ml_list_media_bookmarks( ml, ¶ms, mediaId ) );
},
//UI thread
[this, mediaId](quint64, Ctx& ctx)
{
beginResetModel();
if (m_currentMediaId == mediaId)
m_bookmarks = std::move(ctx.newBookmarks);
else
m_bookmarks.reset();
endResetModel();
});
}
}
vlc_player_t * MLBookmarkModel::player() const
{
return m_player;
}
void MLBookmarkModel::setPlayer(vlc_player_t * player)
{
if (m_player == player) return;
if(m_player && m_listener){
vlc_player_locker lock{ m_player };
vlc_player_RemoveListener( m_player, m_listener );
}
m_player = player;
if (m_player && m_mediaLib)
initModel();
}
PlayerController * MLBookmarkModel::playerController() const
{
return m_player_controller;
}
void MLBookmarkModel::setPlayer(PlayerController * player)
{
if (m_player_controller == player) return;
if (m_player_controller)
disconnect(m_player_controller, nullptr, this, nullptr);
m_player_controller = player;
connect(m_player_controller, &PlayerController::lengthChanged, this, &MLBookmarkModel::playerLengthChanged);
setPlayer(player->getPlayer());
}
MediaLib* MLBookmarkModel::ml() const
{
return m_mediaLib;
}
void MLBookmarkModel::playerLengthChanged()
{
refresh(MLBOOKMARKMODEL_REFRESH);
}
void MLBookmarkModel::setMl(MediaLib* medialib)
{
if(m_mediaLib == medialib) return;
if(m_mediaLib)
m_ml_event_handle.reset(nullptr);
m_mediaLib = medialib;
if (m_player && m_mediaLib)
initModel();
}
void MLBookmarkModel::initModel()
{
static const vlc_player_cbs cbs = []{
vlc_player_cbs cbs {};
cbs.on_current_media_changed = &onCurrentMediaChanged;
cbs.on_state_changed = &onPlaybackStateChanged;
return cbs;
}();
QString uri;
{
vlc_player_locker lock{ m_player };
vlc::threads::mutex_locker selflock{ m_mutex };
m_listener = vlc_player_AddListener( m_player, &cbs, this );
if ( m_listener == nullptr )
throw std::bad_alloc{};
auto currentItem = vlc_player_GetCurrentMedia( m_player );
m_currentItem = vlc::wrap_cptr( currentItem ? input_item_Hold( currentItem ) : nullptr,
&input_item_Release );
if (m_currentItem)
{
uri = m_currentItem->psz_uri;
}
}
updateMediaId(0, uri);
if (m_ml_event_handle == nullptr)
m_ml_event_handle.reset(m_mediaLib->registerEventListener(onVlcMlEvent, this));
}
void MLBookmarkModel::onVlcMlEvent(void* data, const vlc_ml_event_t* event)
{
auto self = static_cast<MLBookmarkModel*>(data);
auto mlEvent = std::make_shared<MLEvent>(event);
QMetaObject::invokeMethod(self, [self, mlEvent] () mutable {
self->onVlcMlEvent(*mlEvent);
});
}
void MLBookmarkModel::onVlcMlEvent(const MLEvent &event){
switch (event.i_type)
{
case VLC_ML_EVENT_BOOKMARKS_ADDED:
case VLC_ML_EVENT_BOOKMARKS_DELETED:
case VLC_ML_EVENT_BOOKMARKS_UPDATED:
refresh(MLBOOKMARKMODEL_REFRESH);
break;
default:
break;
}
}
¤ Dauer der Verarbeitung: 0.9 Sekunden
¤
*© Formatika GbR, Deutschland
|
|