Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions QuiteRSS.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
<file alias="newspaper_description">html/newspaper_description.html</file>
<file alias="newspaper_head">html/newspaper_head.html</file>
<file alias="newspaper_description_rtl">html/newspaper_description_rtl.html</file>
<file alias="sequential_youtube_player">html/sequential_youtube_player.html</file>
</qresource>
<qresource prefix="/flags">
<file alias="flag_AR">images/flags/flag_arab.png</file>
Expand Down
244 changes: 244 additions & 0 deletions html/sequential_youtube_player.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube Sequential Player</title>
<style>
body {
background-color: black;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: auto;
margin: 0;
color: white;
text-align: center;
overflow: auto;
}
#player-container {
margin-bottom: 20px;
}
#current-title {
font-size: 20px;
margin-bottom: 10px;
color: white;
}
#queue {
list-style-type: none;
padding: 0;
margin: 0;
width: auto;
}
#queue li {
background-color: #333;
padding: 10px;
margin-bottom: 5px;
cursor: pointer;
color: white;
border-radius: 5px;
text-align: left;
user-select: none;
display: flex;
align-items: center;
position: relative;
}
#queue li.current {
background-color: #f0f0f0;
color: black;
font-weight: bold;
}
#controls {
margin-top: 20px;
}
a {
color: white;
margin: 0 15px;
cursor: pointer;
text-decoration: none;
padding: 8px 12px;
border: 2px solid white;
border-radius: 5px;
transition: background-color 0.3s;
}
a:hover {
background-color: white;
color: black;
}
a.disabled {
color: grey;
cursor: not-allowed;
border-color: grey;
}
</style>
<script>
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

var videos = [%1];

var currentIndex = 0;
var player;

function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: videos[currentIndex].id,
playerVars: {
'autoplay': 1,
'controls': 1,
'rel': 0,
'modestbranding': 1
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});

updateQueueUI();
}

function onPlayerReady() {
updateControls();
refocusPlayer();
}

function onPlayerStateChange(event) {
if (event.data === YT.PlayerState.ENDED) {
loadNextVideo();
}
}

function loadVideoAtIndex(index) {
currentIndex = index;
player.loadVideoById(videos[currentIndex].id);
updateControls();
refocusPlayer();
updateQueueUI();
}

function loadPreviousVideo() {
if (currentIndex > 0) {
currentIndex--;
player.loadVideoById(videos[currentIndex].id);
updateControls();
refocusPlayer();
updateQueueUI();
}
}

function loadNextVideo() {
if (currentIndex < videos.length - 1) {
currentIndex++;
player.loadVideoById(videos[currentIndex].id);
updateControls();
refocusPlayer();
updateQueueUI();
}
}

function refocusPlayer() {
var iframe = document.getElementById('player');
if (iframe) {
iframe.focus();
}
}

function updateControls() {
var prevLink = document.getElementById('prev');
var nextLink = document.getElementById('next');

prevLink.classList.toggle('disabled', currentIndex === 0);
nextLink.classList.toggle('disabled', currentIndex === videos.length - 1);
}

function updateQueueUI() {
var queue = document.getElementById('queue');
var currentTitle = document.getElementById('current-title');
queue.innerHTML = '';
currentTitle.textContent = videos[currentIndex].title;

videos.forEach((video, index) => {
var listItem = document.createElement('li');
listItem.textContent = `${index + 1}. ${video.title}`;
listItem.dataset.index = index;

if (index === currentIndex) {
listItem.classList.add('current');
}

listItem.addEventListener('click', function() {
loadVideoAtIndex(index);
});

queue.appendChild(listItem);
});
}

document.addEventListener('keydown', function (event) {
const theKey = event.key.toLowerCase();
switch (theKey) {
case 'm': // Mute/unmute the video
const isMuted = player.isMuted();
if (isMuted) {
player.unMute();
} else {
player.mute();
}
break;
case 'f': // Enter/exit fullscreen
if (!document.fullscreenElement) {
const iframe = player.getIframe();
if (iframe) {
iframe.requestFullscreen();
}
} else {
document.exitFullscreen();
}
break;
case 'n': // Play next video
case 'enter':
loadNextVideo();
break;
case 'p': // Play previous video
loadPreviousVideo();
break;
case ' ': // Play/pause the video
event.preventDefault();
const playerState = player.getPlayerState();
if (playerState === YT.PlayerState.PAUSED || playerState === YT.PlayerState.ENDED) {
player.playVideo();
} else if (playerState === YT.PlayerState.PLAYING) {
player.pauseVideo();
}
break;
case 'arrowright': // Skip forward 5 seconds
const currentTimeForward = player.getCurrentTime();
player.seekTo(currentTimeForward + 5, true);
break;
case 'arrowleft': // Skip backward 5 seconds
const currentTimeBackward = player.getCurrentTime();
player.seekTo(Math.max(0, currentTimeBackward - 5), true);
break;
}
});
</script>
</head>
<body>
<div id="player-container">
<div id="current-title"></div>
<div id="player"></div>
<div id="controls">
<a id="prev" class="disabled" onclick="loadPreviousVideo()">Previous</a>
<a id="next" onclick="loadNextVideo()">Next</a>
</div>
</div>

<ul id="queue"></ul>
</body>
</html>
7 changes: 7 additions & 0 deletions src/application/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,8 @@ void MainWindow::createFeedsWidget()
this, SLOT(clearDeleted()));
connect(categoriesTree_, SIGNAL(signalMarkRead(QTreeWidgetItem*)),
this, SLOT(slotMarkReadCategory(QTreeWidgetItem*)));
connect(categoriesTree_, SIGNAL(signalViewAllYoutubeVideos()),
this, SLOT(viewAllYoutubeVideos()));
connect(showCategoriesButton_, SIGNAL(clicked()),
this, SLOT(showNewsCategoriesTree()));
connect(feedsSplitter_, SIGNAL(splitterMoved(int,int)),
Expand Down Expand Up @@ -7585,6 +7587,11 @@ void MainWindow::slotMarkReadCategory(QTreeWidgetItem *item)
}
}

void MainWindow::viewAllYoutubeVideos()
{
currentNewsTab->viewAllYoutubeVideos();
}

/** @brief Show/Hide categories tree
*---------------------------------------------------------------------------*/
void MainWindow::showNewsCategoriesTree()
Expand Down
1 change: 1 addition & 0 deletions src/application/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ private slots:
void slotCategoriesClicked(QTreeWidgetItem *item, int, bool createTab = false);
void clearDeleted();
void slotMarkReadCategory(QTreeWidgetItem *item);
void viewAllYoutubeVideos();
void showNewsCategoriesTree();
void feedsSplitterMoved(int pos, int);

Expand Down
4 changes: 4 additions & 0 deletions src/categoriestreewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ void CategoriesTreeWidget::showContextMenuCategory(const QPoint &pos)
menu.addSeparator();
menu.addAction(tr("Mark Read"), this, SLOT(slotMarkRead()));
}
if (itemClicked_ == topLevelItem(UnreadItem)) {
menu.addSeparator();
menu.addAction(tr("View All Youtube Videos (Experimental)"), this, SIGNAL(signalViewAllYoutubeVideos()));
}
menu.exec(viewport()->mapToGlobal(pos));
}
}
Expand Down
1 change: 1 addition & 0 deletions src/categoriestreewidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class CategoriesTreeWidget : public QTreeWidget
void signalMiddleClicked();
void signalClearDeleted();
void signalMarkRead(QTreeWidgetItem *item);
void signalViewAllYoutubeVideos();
// void pressKeyUp();
// void pressKeyDown();

Expand Down
59 changes: 59 additions & 0 deletions src/newstabwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,65 @@ void NewsTabWidget::markAllNewsRead()
mainWindow_->recountCategoryCounts();
}

void NewsTabWidget::viewAllYoutubeVideos()
{
if (type_ != TabTypeUnread) return;
int cnt = newsModel_->rowCount();
if (cnt == 0) return;
struct YoutubeVideoData
{
QString VideoTitle;
QString VideoId;
};
QList<YoutubeVideoData> allYoutubeVideos;
for (int i = cnt-1; i > -1; --i) {
bool read = (newsModel_->dataField(i, "read").toInt() > 0);
if (read) continue; //they might still be in the Unread category but just recently read moments ago
QUrl htmlUrl = QUrl::fromEncoded(getLinkNews(i).toUtf8());
QString host = htmlUrl.host();
if (host == "youtube.com" || host == "www.youtube.com") {
QUrlQuery query(htmlUrl);
QString videoId = query.queryItemValue("v");
if (!videoId.isEmpty()) {
QString videoTitle = newsModel_->dataField(i, "title").toString();
allYoutubeVideos.append(YoutubeVideoData{videoTitle, videoId});
}
}
}
if (allYoutubeVideos.isEmpty()) return;
QFile sequentialYoutubePlayerHtmlFile;
sequentialYoutubePlayerHtmlFile.setFileName(":/html/sequential_youtube_player");
sequentialYoutubePlayerHtmlFile.open(QFile::ReadOnly);
QString sequentialYoutubePlayerHtml = QString::fromUtf8(sequentialYoutubePlayerHtmlFile.readAll());
sequentialYoutubePlayerHtmlFile.close();

QString videoTitlesAndIdsJavascript;
bool first = true;
foreach (const YoutubeVideoData &ytVideo, allYoutubeVideos) {
if (!first) {
videoTitlesAndIdsJavascript += ",\n";
}
first = false;
QString jsTitle = ytVideo.VideoTitle;
jsTitle.replace("\"", "\\\"");
QString jsId = ytVideo.VideoId;
jsId.replace("\"", "\\\"");
videoTitlesAndIdsJavascript += "{ title: \"" + jsTitle + "\", id: \"" + jsId + "\" }";
}

sequentialYoutubePlayerHtml = sequentialYoutubePlayerHtml.arg(videoTitlesAndIdsJavascript);

QTemporaryFile tempFile(QDir::tempPath() + "/sequential-youtube-player-XXXXXX.html");
if (tempFile.open()) {
tempFile.setAutoRemove(false);
QTextStream tempFileStream(&tempFile);
tempFileStream << sequentialYoutubePlayerHtml;
tempFile.close();
QUrl tempFileUrl = "file://" + tempFile.fileName();
openUrl(tempFileUrl);
}
}

/** @brief Mark selected news Starred
*----------------------------------------------------------------------------*/
void NewsTabWidget::markNewsStar()
Expand Down
1 change: 1 addition & 0 deletions src/newstabwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class NewsTabWidget : public QWidget
void setBrowserPosition();
void markNewsRead();
void markAllNewsRead();
void viewAllYoutubeVideos();
void markNewsStar();
void setLabelNews(int labelId);
void deleteNews();
Expand Down