Add support for chapters in episodes
This commit is contained in:
parent
bb20181a77
commit
69c803ab5c
7 changed files with 160 additions and 0 deletions
|
@ -26,6 +26,7 @@ MediaPlayer {
|
||||||
|
|
||||||
property int episode: 0
|
property int episode: 0
|
||||||
property string episode_title: ''
|
property string episode_title: ''
|
||||||
|
property var episode_chapters: ([])
|
||||||
property string podcast_title: ''
|
property string podcast_title: ''
|
||||||
signal playerCreated()
|
signal playerCreated()
|
||||||
|
|
||||||
|
@ -62,6 +63,7 @@ MediaPlayer {
|
||||||
var old_episode = player.episode;
|
var old_episode = player.episode;
|
||||||
player.episode = episode_id;
|
player.episode = episode_id;
|
||||||
player.episode_title = episode.title;
|
player.episode_title = episode.title;
|
||||||
|
player.episode_chapters = episode.chapters;
|
||||||
player.podcast_title = episode.podcast_title;
|
player.podcast_title = episode.podcast_title;
|
||||||
player.source = episode.source;
|
player.source = episode.source;
|
||||||
player.seekTargetSeconds = episode.position;
|
player.seekTargetSeconds = episode.position;
|
||||||
|
|
2
main.py
2
main.py
|
@ -327,6 +327,7 @@ class gPotherSide:
|
||||||
'position': episode.current_position,
|
'position': episode.current_position,
|
||||||
'total': episode.total_time,
|
'total': episode.total_time,
|
||||||
'video': episode.file_type() == 'video',
|
'video': episode.file_type() == 'video',
|
||||||
|
'chapters': getattr(episode, 'chapters', []),
|
||||||
}
|
}
|
||||||
|
|
||||||
def report_playback_event(self, episode_id, position_from, position_to, duration):
|
def report_playback_event(self, episode_id, position_from, position_to, duration):
|
||||||
|
@ -345,6 +346,7 @@ class gPotherSide:
|
||||||
'description': util.remove_html_tags(episode.description),
|
'description': util.remove_html_tags(episode.description),
|
||||||
'metadata': ' | '.join(self._format_metadata(episode)),
|
'metadata': ' | '.join(self._format_metadata(episode)),
|
||||||
'link': episode.link if episode.link != episode.url else '',
|
'link': episode.link if episode.link != episode.url else '',
|
||||||
|
'chapters': getattr(episode, 'chapters', []),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _format_metadata(self, episode):
|
def _format_metadata(self, episode):
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
|
|
||||||
import 'common/constants.js' as Constants
|
import 'common/constants.js' as Constants
|
||||||
|
import 'common/util.js' as Util
|
||||||
import 'icons/icons.js' as Icons
|
import 'icons/icons.js' as Icons
|
||||||
|
|
||||||
SlidePage {
|
SlidePage {
|
||||||
|
@ -30,6 +31,7 @@ SlidePage {
|
||||||
property string title
|
property string title
|
||||||
property string link
|
property string link
|
||||||
property bool ready: false
|
property bool ready: false
|
||||||
|
property var chapters: ([])
|
||||||
|
|
||||||
hasMenuButton: detailPage.link != ''
|
hasMenuButton: detailPage.link != ''
|
||||||
menuButtonIcon: Icons.link
|
menuButtonIcon: Icons.link
|
||||||
|
@ -47,6 +49,7 @@ SlidePage {
|
||||||
metadataLabel.text = episode.metadata;
|
metadataLabel.text = episode.metadata;
|
||||||
detailPage.link = episode.link;
|
detailPage.link = episode.link;
|
||||||
detailPage.ready = true;
|
detailPage.ready = true;
|
||||||
|
detailPage.chapters = episode.chapters;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,6 +90,45 @@ SlidePage {
|
||||||
color: Constants.colors.placeholder
|
color: Constants.colors.placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PExpander {
|
||||||
|
visible: detailPage.chapters.length > 0
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
expandedHeight: chaptersColumn.childrenRect.height
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: chaptersColumn
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
PLabel {
|
||||||
|
text: 'Chapters'
|
||||||
|
color: Constants.colors.secondaryHighlight
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: detailPage.chapters
|
||||||
|
|
||||||
|
delegate: Column {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
PLabel {
|
||||||
|
width: parent.width
|
||||||
|
text: Util.formatDuration(modelData.start)
|
||||||
|
font.pixelSize: 20 * pgst.scalef
|
||||||
|
color: Constants.colors.secondaryHighlight
|
||||||
|
}
|
||||||
|
|
||||||
|
PLabel {
|
||||||
|
width: parent.width
|
||||||
|
text: modelData.title
|
||||||
|
font.pixelSize: 20 * pgst.scalef
|
||||||
|
color: Constants.colors.placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PLabel {
|
PLabel {
|
||||||
id: descriptionLabel
|
id: descriptionLabel
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
88
touch/PExpander.qml
Normal file
88
touch/PExpander.qml
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* gPodder QML UI Reference Implementation
|
||||||
|
* Copyright (c) 2014, Thomas Perl <m@thp.io>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
* PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import QtQuick 2.0
|
||||||
|
|
||||||
|
import 'common/constants.js' as Constants
|
||||||
|
import 'common/util.js' as Util
|
||||||
|
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: expander
|
||||||
|
|
||||||
|
property bool expanded: false
|
||||||
|
property bool canExpand: expandedHeight > contractedHeight
|
||||||
|
|
||||||
|
property real contractedHeight: 100 * pgst.scalef
|
||||||
|
property real expandedHeight: childrenRect.height
|
||||||
|
property color backgroundColor: Constants.colors.page
|
||||||
|
|
||||||
|
height: expanded ? expandedHeight : contractedHeight
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Behavior on height { PropertyAnimation { } }
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
if (expander.canExpand) {
|
||||||
|
expander.expanded = !expander.expanded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
z: 100
|
||||||
|
|
||||||
|
opacity: chapterExpander.opacity
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
height: expander.contractedHeight * 0.6
|
||||||
|
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0; color: '#00000000' }
|
||||||
|
GradientStop { position: 1; color: expander.backgroundColor }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PLabel {
|
||||||
|
id: chapterExpander
|
||||||
|
|
||||||
|
z: 200
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
right: parent.right
|
||||||
|
bottom: parent.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
text: '...'
|
||||||
|
font.pixelSize: 60 * pgst.scalef
|
||||||
|
|
||||||
|
color: Constants.colors.highlight
|
||||||
|
opacity: !expander.expanded
|
||||||
|
|
||||||
|
Behavior on opacity { PropertyAnimation { } }
|
||||||
|
}
|
||||||
|
}
|
|
@ -135,6 +135,29 @@ Dialog {
|
||||||
icon: Icons.last
|
icon: Icons.last
|
||||||
onClicked: player.seekAndSync(player.position + 60 * 1000);
|
onClicked: player.seekAndSync(player.position + 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconMenuItem {
|
||||||
|
text: 'Chapters'
|
||||||
|
color: Constants.colors.playback
|
||||||
|
icon: Icons.tag_fill
|
||||||
|
visible: player.episode_chapters.length > 0
|
||||||
|
onClicked: {
|
||||||
|
var items = [];
|
||||||
|
|
||||||
|
for (var i in player.episode_chapters) {
|
||||||
|
(function (items, chapter) {
|
||||||
|
items.push({
|
||||||
|
label: chapter.title + ' (' + Util.formatDuration(chapter.start) + ')',
|
||||||
|
callback: function () {
|
||||||
|
player.seekAndSync(chapter.start * 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})(items, player.episode_chapters[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pgst.showSelection(items, 'Chapters');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ Dialog {
|
||||||
PLabel {
|
PLabel {
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
margins: 20 * pgst.scalef
|
margins: 20 * pgst.scalef
|
||||||
}
|
}
|
||||||
|
@ -74,6 +75,7 @@ Dialog {
|
||||||
text: modelData
|
text: modelData
|
||||||
color: (index == selectionDialog.selectedIndex || buttonArea.pressed) ? Constants.colors.dialogHighlight : Constants.colors.dialogText
|
color: (index == selectionDialog.selectedIndex || buttonArea.pressed) ? Constants.colors.dialogHighlight : Constants.colors.dialogText
|
||||||
font.pixelSize: 30 * pgst.scalef
|
font.pixelSize: 30 * pgst.scalef
|
||||||
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
|
|
@ -21,3 +21,4 @@ var cog = '\u2699';
|
||||||
var link = '\ue077';
|
var link = '\ue077';
|
||||||
var vellipsis = '\u22ee';
|
var vellipsis = '\u22ee';
|
||||||
var paperclip = '\ue08a';
|
var paperclip = '\ue08a';
|
||||||
|
var tag_fill = '\ue02b';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue