Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add podcast support #875

Merged
merged 69 commits into from
Aug 20, 2021
Merged

Add podcast support #875

merged 69 commits into from
Aug 20, 2021

Conversation

paulijar
Copy link
Collaborator

@paulijar paulijar commented Aug 5, 2021

This PR implements the support for podcasts as requested in #786.

This turned out to be one of the most extensive features ever implemented for the project within a single PR. This is largely because the feature has inter-connections with so many other features. Some of the implemented sub-features include:

  • Dedicated view on the web UI
    • The view is similar to the Albums view but instead of two level of headings (artists and albums) there is only a single level (podcast channels)
    • Unlike the Albums view, the Podcasts view has no "compact layout" mode. The assumption is that users will not have hundreds or thousands of subscribed channels, making a more compact view not so useful.
  • Adding new podcast channels by entering the URL of the RSS feed
  • Checking for new episodes manually or automatically on the background by schedule
  • Details pane for podcast channels and episodes
    • The pane supports also HTML-formatted channel/episode descriptions
  • Searching/filtering in the podcasts view by channel and episode titles
  • Subsonic API
    • Added methods getPodcasts, getNewestPodcasts, refreshPodcasts, createPodcastChannel, deletePodcastChannel
    • Support starring also podcast episodes and channels with methods star and unstar
    • Support for podcast episodes on methods createBookmark and getBookmarks
  • Ampache API
    • Added methods podcasts, podcast, podcast_create, podcast_delete, podcast_episodes, podcast_episode, update_podcast
    • Support podcast types in methods get_indexes, stats, flag, get_art, download, stream
  • Command line support with the occ tool
    • New commands music:podcast-add, music:podcast-reset, music:podcast-update

image

For now, the back-end is just able to return two hard-coded podcast
channels when all the channels are requested. Everything else is "TODO".
The view can show the podcast channels and episoded provided by the
back-end but cannot play them yet. The file podcastsviewcontroller.js
still contains a lot of copy-pasted code from the
albumsviewcontroller.js which should be refitted or removed.
The listheading widget was being rendered incorrectly in case the
element had no details button enabled.
The podcast episode, channnel, and image are now properly shown in the
player control bar, in the mediaSession integration, and in the desktop
notification.
All these new classes and definitions are not yet used for anything.
The `occ` command `music:podcast-add` can now be used to add a single podcast
channel from a RSS feed and the command `music:podcast-reset` can be used to
reset all the previously added channels along with their episodes.
Get the data from the database tables instead of fetching it on the fly from
a few hard-coded RSS feed URLs.
Implemented REST API endpoints to subscribe to a podcast channel, to
unsubscribe it, to get an individual podcast channel and to reset all the
subscribed channels.
It is at least theoretically possible that there would we identical podcast
channels with differing RSS feed URLs. In such case, we should allow
duplicate episode `guid`s to exist.
As part of this change, a new module podcstService was created to host
the new functions. Also the handling of the creating/subscribing a new
podcast channel was moved from navigationController to this new service.
To achieve this, a support had to be added to the listHeading directive
to attach an actions menu. The menu contents are injected when
instantiating the directive.
The new instance used to be always appended to the end of all instances
although often this was not the correct place, according the order of
the elements on the UI. Hence, the lazy loading didn't work correctly
before reinitializing the view by navigating to another view and back.
On some podcast channels, the <lastBuildDate> tag changes very often,
e.g. once every 15 minutes. This caused a lot of unnecessary updates on
the database contents with such channels.
- The command now supports defining multiple RSS feed URLs at once

- When adding a feed for multiple users, the feed is now fetched and
  parsed only once, and not separately for each user
This facilitates code reuse when adding the podcast support for the
Subsonic and Ampache APIs: The class PodcastService contains
API-agnostic logic while PodcastApiController is just a thin wrapper
for it, providing the adaptation to the REST API provided for the web
UI.
The entity classes PodcastChannel and PodcastEpisode now have functions
called toSubsonicApi. These replace the former functions
SubsonicController::podcastChannelToApi and
SubsonicController::podcastChannelToApi. Similar design was previously
applied to the Ampache API conversion of the podcast entity types. The
target of this refactoring is to reduce the size of the monolithic
SubsonicController.

Similar refactoring should be eventually made to the other entity
types, too, but that does not belong under this feature branch.
This same piece of logic, truncating a long HTML description and expanding it
on click, was repeated three times in the various details templates. Now it
is a reusable directive which opens the road to apply it also on the podcast
descriptions (which is not done yet).
The full description may be viewed by clicking the truncated description.
The option may be give to force the updating of the episodes of all the
targeted podcast channels, even when the podcast feed has not changed at all
since the previous update. This may be desirable during the development when
the parsing of the feed is changed by any means. It might be useful for the
end-user, too, in case the database update somehow got unexpectedly aborted
during the previous attempt.
- Extract also the season number from the podcast feed if available

- The episode number is shown in the Podcasts view for each episode where
  available. If there is also a season number, the episode number is prefixed
  with it like "season-episode", e.g. "3-14.".

- Sometimes also the title field in the RSS field contains the episode
  numbers. If the same number is present separately and as part of the title,
  then attempt to strip it off the title.

- The season number was added to the episode details pane, too

- The episode number was added to the Subsonic responses as property `track`.
  According to the Subsonic XML schema, the podcast episodes may contain all
  the same properties as the "normal" audio tracks. However, the tested
  clients (DSub and Ultrasonic) didn't seem to show the `track` property on
  episodes.
When subscribing to a podcast channel, add the available episodes to the DB
in reverse order compared to the order of the RSS feed. Typically, the most
recent episode is the first in the list, so this means adding the episodes
from oldest to newest. Then, when new episodes are published, they will
obviously be added after the first patch of the episodes, maintaining the
same "oldest to newest" logic. Then, when returning the episodes through the
REST API, the list is reversed once more to get the newest episodes first.
The play (or pause) icon was not shown in the episode list on the Podcasts
view in front of the currently playing (or paused) episode.
…null"

Many columns were accidentally marked as "not null" although they may quite
well be missing in the RSS feed. This didn't break the application because
the implementation details in the BusinessLayer were such that empty strings
got stored instead of null values.
The`occ music:podcast-add` was actually totally broken because of a
copy-paste error.
This is probably rarely useful but should be there for consistency of the UX
with all the other views.
…cally

When clicking the title or image of a podcast channel, the contained episodes
are now enqueue to the play queue in chronological order. That is, they are
played from bottom to top.
It's not quite clear if this method should return the episodes according to
their original publish dates or by the order in which they have been added to
the database. Now it's doing the latter which was a bit easier to achieve.

The Subsonic API version has been bumbed to 1.13.0 to advertise the
availability of this method; at least DSub uses only methods belonging to the
claimed API version. The other new methods from v1.12.0 and 1.13.0 are
`getTopSongs`, `getPlayQueue`, and `savePlayQueue`, which have been stubbed
for now.
There's no technical reason, why there couldn't be collisions between
track IDs and podcast episode IDs. This has to be taken into account
when evaluating if a track should be visualized as the "current track"
or not.
NC core provides suitable styling for this but OC core does not. Hence,
we need to define the style on our own. The correct kind of styling
already existed in style-navigation.css but that was applied only
within the actions menu. These style definitions were now moved to
their own file style-actions-menu.css, and they were made to apply both
on the navigation pane and elsewhere within the Music app.
Previously, while there were no podcast channels, the actions menu item
"Reload channels" only appeared disabled but it could actually be
clicked. This triggered a toast with text "All channels were already
up-to-date".
Shown while waiting for the contents for the pane.
When the details pane (for any content type) was closed and then opened
again for any content type requiring closing, there was a flicker when
the previous content was visible on the screen for a fraction of a
second before being replaced by the loading spinner. This was visible
at least when opening details pane for a podcast channel/episode, a
track, or an album.
Also, when creating the first channel, there is now a loading spinner
shown in the view.

To facilitate all this, all methods of PodcastService were modified to
return promises. This replaced signaling via $rootScope.$emit which
was really designed only to be used when triggering operations from the
navigation pane actions menu.
The same format is used also on the response of the Ampache action
`handshake`. The example responses in the Ampache API documentation
actually use the American format like "08/19/21 22:53 PM" but I hope
that this is just an error in the spec and/or in Ampache SW since using
ambiguous format like that in any API makes no sense.
This timestamp is part of the specified Ampache podcast responses so we
need it after all. It is now also included in the channel details shown
on the web UI. This field is still excluded when judging if the RSS
feed has actually changed after the previous podcast update (because on
some channels, the lastBuildDate changes very ofter without any other
changes). This means that if only the lastBuildDate is updated in the
feed, then the user will not see this even with manual update from the
UI.
The `--force` flag now forces an update also on the data stored in the
oc_music_podcast_channels table. Previously, the forcing was applied
only on the episodes table.
Scrutinizer doesn't know that object of type SimpleXMLElement may be
evaluated as false, hence claim that the argument could be actually
null.
@paulijar paulijar changed the title [WIP] Add podcast support Add podcast support Aug 20, 2021
The support was almost there, but enabling it for this content type had
been forgotten previously.
@scrutinizer-notifier
Copy link

The inspection completed: 129 updated code elements

@paulijar paulijar merged commit d5b8efb into master Aug 20, 2021
@delete-merged-branch delete-merged-branch bot deleted the feature/786_podcasts branch August 20, 2021 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants