Compare commits

...

11 Commits

Author SHA1 Message Date
Wiktor Zykubek
45b5643cc7
Update screenshot_linux.png 2023-06-19 01:13:55 +02:00
Wiktor Zykubek
eeadc0a8c2
Create separate pause and stop button 2023-06-18 22:27:55 +02:00
Wiktor Zykubek
5358deab51
Change pagination for eposiodes list 2023-06-18 18:01:06 +02:00
Wiktor Zykubek
f664548d71
Allow to play podcasts 2023-06-18 17:57:27 +02:00
Wiktor Zykubek
c26d8833b3
Clear stationsListWidget each time listWidget is changed 2023-06-18 17:46:16 +02:00
Wiktor Zykubek
32ed78e7bc
Increase MainWindow width 2023-06-18 17:42:55 +02:00
Wiktor Zykubek
edc498c510
Print podcasts and episodes 2023-06-18 17:37:10 +02:00
Wiktor Zykubek
d02f7302a2
Add printPodcastGroups method 2023-06-18 16:37:12 +02:00
Wiktor Zykubek
e01edba7f3
Rename methods 2023-06-18 16:34:19 +02:00
Wiktor Zykubek
0b63621532
Adjust and rename getStationsData method to handle other URLs 2023-06-18 16:32:57 +02:00
Wiktor Zykubek
89cedd4f7c
Add tabWidget and podcasts category to UI 2023-06-18 16:14:26 +02:00
5 changed files with 191 additions and 36 deletions

101
form.ui
View File

@ -6,19 +6,19 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>350</width> <width>650</width>
<height>500</height> <height>500</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>350</width> <width>650</width>
<height>500</height> <height>500</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>350</width> <width>650</width>
<height>500</height> <height>500</height>
</size> </size>
</property> </property>
@ -44,7 +44,7 @@
<rect> <rect>
<x>9</x> <x>9</x>
<y>9</y> <y>9</y>
<width>332</width> <width>631</width>
<height>461</height> <height>461</height>
</rect> </rect>
</property> </property>
@ -52,19 +52,85 @@
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QListWidget" name="groupsListWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>130</width> <width>200</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>130</width> <width>200</width>
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="radioTab">
<attribute name="title">
<string>Radio</string>
</attribute>
<widget class="QListWidget" name="radioGroupsListWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>200</width>
<height>400</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>400</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>400</height>
</size>
</property>
</widget>
</widget>
<widget class="QWidget" name="podcastTab">
<attribute name="title">
<string>Podcasty</string>
</attribute>
<widget class="QWidget" name="verticalLayoutWidget_2">
<property name="geometry">
<rect>
<x>-1</x>
<y>-1</y>
<width>201</width>
<height>401</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListWidget" name="podcastGroupsListWidget"/>
</item>
<item>
<widget class="QListWidget" name="podcastListWidget">
<property name="minimumSize">
<size>
<width>0</width>
<height>195</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>195</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget> </widget>
</item> </item>
<item> <item>
@ -81,6 +147,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QToolButton" name="stopToolButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item> <item>
<widget class="Line" name="line"> <widget class="Line" name="line">
<property name="lineWidth"> <property name="lineWidth">
@ -99,6 +172,18 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
<property name="value"> <property name="value">
<number>70</number> <number>70</number>
</property> </property>
@ -124,7 +209,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>350</width> <width>650</width>
<height>22</height> <height>22</height>
</rect> </rect>
</property> </property>

View File

@ -1,6 +1,5 @@
# This Python file uses the following encoding: utf-8 # This Python file uses the following encoding: utf-8
API_URL = "https://open.fm/radio/api/v2/ofm/stations_slug.json"
DEFAULT_VOLUME = 70 DEFAULT_VOLUME = 70
from .mainwindow import MainWindow from .mainwindow import MainWindow

View File

@ -11,7 +11,7 @@ from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput
from ui_form import Ui_MainWindow from ui_form import Ui_MainWindow
import json import json
import requests import requests
from . import API_URL, DEFAULT_VOLUME from . import DEFAULT_VOLUME
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
@ -24,25 +24,36 @@ class MainWindow(QMainWindow):
self.ui.setupUi(self) self.ui.setupUi(self)
volume_icon = self.style().standardIcon(QStyle.SP_MediaVolume) volume_icon = self.style().standardIcon(QStyle.SP_MediaVolume)
playback_icon = self.style().standardIcon(QStyle.SP_MediaPlay) playback_icon = self.style().standardIcon(QStyle.SP_MediaPlay)
stop_icon = self.style().standardIcon(QStyle.SP_MediaStop)
self.ui.volumeToolButton.setIcon(volume_icon) self.ui.volumeToolButton.setIcon(volume_icon)
self.ui.playbackToolButton.setIcon(playback_icon) self.ui.playbackToolButton.setIcon(playback_icon)
self.ui.stopToolButton.setIcon(stop_icon)
self.__stations_data = self.getStationsData() self.__stations = self.getData(
"https://open.fm/radio/api/v2/ofm/stations_slug.json"
)
self.__podcasts_groups = self.getData(
"https://open.fm/api/podcasts/categories"
)
self.__player = QMediaPlayer() self.__player = QMediaPlayer()
self.__audio = QAudioOutput() self.__audio = QAudioOutput()
self.__player.setAudioOutput(self.__audio) self.__player.setAudioOutput(self.__audio)
self.setVolume(DEFAULT_VOLUME) self.setVolume(DEFAULT_VOLUME)
self.printGroups() self.printRadioGroups()
self.printPodcastGroups()
self.ui.groupsListWidget.itemClicked.connect(self.printStations) self.ui.radioGroupsListWidget.itemClicked.connect(self.printRadioStations)
self.ui.podcastGroupsListWidget.itemClicked.connect(self.printPodcasts)
self.ui.podcastListWidget.itemClicked.connect(self.printPodcastEpisodes)
self.ui.stationsListWidget.itemClicked.connect(self.playRadio) self.ui.stationsListWidget.itemClicked.connect(self.playRadio)
self.ui.playbackToolButton.clicked.connect(self.togglePlayer) self.ui.playbackToolButton.clicked.connect(self.togglePlayer)
self.ui.stopToolButton.clicked.connect(self.stopPlayer)
self.ui.volumeHorizontalSlider.valueChanged.connect(self.setVolume) self.ui.volumeHorizontalSlider.valueChanged.connect(self.setVolume)
self.ui.volumeToolButton.clicked.connect(self.toggleMute) self.ui.volumeToolButton.clicked.connect(self.toggleMute)
def getStationsData(self) -> dict: def getData(self, url) -> dict:
"""Get JSON data from API and convert it to dict.""" """Get JSON data from API and convert it to dict."""
resp = requests.get(API_URL) resp = requests.get(url)
if resp.status_code not in range(200, 299 + 1): if resp.status_code not in range(200, 299 + 1):
error_box = QMessageBox.critical( error_box = QMessageBox.critical(
self, self,
@ -54,17 +65,25 @@ class MainWindow(QMainWindow):
else: else:
return json.loads(resp.text) return json.loads(resp.text)
def printGroups(self) -> None: def printRadioGroups(self) -> None:
"""Print groups (categories) in groupsListWidget.""" """Print groups (categories) in radioGroupsListWidget."""
self.ui.groupsListWidget.addItems( self.ui.stationsListWidget.clear()
[e["name"] for e in self.__stations_data["groups"]] self.ui.radioGroupsListWidget.addItems(
[e["name"] for e in self.__stations["groups"]]
) )
def printStations(self) -> None: def printPodcastGroups(self) -> None:
"""Print groups (categories) in podcastGroupsListWidget."""
self.ui.stationsListWidget.clear()
self.ui.podcastGroupsListWidget.addItems(
[e["name"] for e in self.__podcasts_groups]
)
def printRadioStations(self) -> None:
"""Print stations (channels) in stationsListWidget.""" """Print stations (channels) in stationsListWidget."""
group = self.ui.groupsListWidget.selectedItems()[0].text() group = self.ui.radioGroupsListWidget.selectedItems()[0].text()
group_id = None group_id = None
for e in self.__stations_data["groups"]: for e in self.__stations["groups"]:
if e["name"] == group: if e["name"] == group:
group_id = e["id"] group_id = e["id"]
@ -72,11 +91,44 @@ class MainWindow(QMainWindow):
self.ui.stationsListWidget.addItems( self.ui.stationsListWidget.addItems(
[ [
e["name"] e["name"]
for e in self.__stations_data["channels"] for e in self.__stations["channels"]
if e["group_id"] == group_id if e["group_id"] == group_id
] ]
) )
def printPodcasts(self) -> None:
"""Print podcasts in podcastListWidget."""
self.ui.stationsListWidget.clear()
group = self.ui.podcastGroupsListWidget.selectedItems()[0].text()
group_name = None
for e in self.__podcasts_groups:
if e["name"] == group:
group_name = e["slug"]
self.__podcasts = self.getData(
f"https://open.fm/api/podcasts/category/{group_name}"
)["podcasts"]["content"]
self.ui.podcastListWidget.clear()
self.ui.podcastListWidget.addItems(
[e["title"] for e in self.__podcasts]
)
def printPodcastEpisodes(self) -> None:
"""Print podcast episodes in stationsListWidget."""
podcast_name = self.ui.podcastListWidget.selectedItems()[0].text()
podcast_id = None
for e in self.__podcasts:
if e["title"] == podcast_name:
podcast_id = e["id"]
self.__podcast_episodes = self.getData(
f"https://open.fm/api/podcast/{podcast_id}/episodes?size=999"
)
self.ui.stationsListWidget.clear()
self.ui.stationsListWidget.addItems(
[e["title"] for e in self.__podcast_episodes["content"]]
)
def setVolume(self, volume: int = None) -> None: def setVolume(self, volume: int = None) -> None:
"""Set playback volume to given number or slider value.""" """Set playback volume to given number or slider value."""
if not volume: if not volume:
@ -98,30 +150,49 @@ class MainWindow(QMainWindow):
def playRadio(self) -> None: def playRadio(self) -> None:
"""Play station selected by user.""" """Play station selected by user."""
station = self.ui.stationsListWidget.selectedItems()[0].text() selection = self.ui.stationsListWidget.selectedItems()[0].text()
stream_url = None stream_url = None
for e in self.__stations_data["channels"]: for e in self.__stations["channels"]:
if e["name"] == station: if e["name"] == selection:
stream_url = f"http://stream.open.fm/{e['id']}" stream_url = f"http://stream.open.fm/{e['id']}"
self.__player.setSource(QUrl(stream_url)) if not stream_url:
for e in self.__podcast_episodes["content"]:
if e["title"] == selection:
stream_url = e["file"].replace("https", "http")
# Required to avoid crashing. For some reason if you want to change self.__player.setSource(QUrl(stream_url))
# the station for the first time, you need to stop and resume playback. self.__fix_playback()
def __fix_playback(self) -> None:
"""Fix playack."""
# For some reason if you want to change the station for the first time,
# you need to stop and resume playback.
# If you won't, application would crash. # If you won't, application would crash.
for _ in range(3): self.__player.play()
self.togglePlayer() self.__player.stop()
self.__player.play()
icon = self.style().standardIcon(QStyle.SP_MediaPause)
self.ui.playbackToolButton.setIcon(icon)
def togglePlayer(self) -> None: def togglePlayer(self) -> None:
"""Toggle playback (play/stop).""" """Toggle playback (play/pause)."""
pb_state = QMediaPlayer.PlaybackState pb_state = QMediaPlayer.PlaybackState
if self.__player.playbackState() == pb_state.PlayingState: if self.__player.playbackState() == pb_state.PlayingState:
self.__player.stop() self.__player.pause()
icon = self.style().standardIcon(QStyle.SP_MediaPlay) icon = self.style().standardIcon(QStyle.SP_MediaPlay)
elif self.__player.playbackState() == pb_state.StoppedState: elif self.__player.playbackState() in [
pb_state.PausedState,
pb_state.StoppedState
]:
self.__player.play() self.__player.play()
icon = self.style().standardIcon(QStyle.SP_MediaStop) icon = self.style().standardIcon(QStyle.SP_MediaPause)
else: else:
pass pass
self.ui.playbackToolButton.setIcon(icon) self.ui.playbackToolButton.setIcon(icon)
def stopPlayer(self) -> None:
self.__player.stop()
icon = self.style().standardIcon(QStyle.SP_MediaPlay)
self.ui.playbackToolButton.setIcon(icon)

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "openfm-qt" name = "openfm-qt"
version = "0.1.0" version = "0.2.0"
description = "Qt client for Polish internet radio Open FM." description = "Qt client for Polish internet radio Open FM."
authors = ["Wiktor Zykubek <wz-git@mailbox.org>"] authors = ["Wiktor Zykubek <wz-git@mailbox.org>"]
license = "ISC" license = "ISC"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 53 KiB