From 6df7c8a353dd73052ac9e05208e8dbe9ab7708e5 Mon Sep 17 00:00:00 2001 From: Evgeny Varnakov Date: Mon, 31 Jul 2023 17:31:02 +0300 Subject: [PATCH] solution: day 3 - task 2 --- app/assets/stylesheets/player.css | 16 ++++++++++ app/channels/listener_channel.rb | 31 +++++++++++++++++++ app/controllers/live_stations_controller.rb | 1 + app/javascript/application.js | 1 + app/javascript/channels/consumer.js | 6 ++++ app/javascript/channels/index.js | 0 .../controllers/player_controller.js | 21 +++++++++++-- app/models/live_station.rb | 18 +++++++++++ app/views/icons/_listeners.html | 3 ++ app/views/player/_player.html.erb | 7 +++++ config/importmap.rb | 2 ++ 11 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 app/channels/listener_channel.rb create mode 100644 app/javascript/channels/consumer.js create mode 100644 app/javascript/channels/index.js create mode 100644 app/views/icons/_listeners.html diff --git a/app/assets/stylesheets/player.css b/app/assets/stylesheets/player.css index 74ec806..2f4590f 100644 --- a/app/assets/stylesheets/player.css +++ b/app/assets/stylesheets/player.css @@ -89,3 +89,19 @@ @apply text-secondary; animation: pulse 1s infinite; } + +.player--listeners { + @apply text-white text-small w-full flex flex-row items-center bg-primary rounded-md ml-2; +} + +.player--listeners--icon { + @apply text-white flex items-center; +} + +.player--listeners--icon > svg { + @apply h-5; +} + +.player--listeners--count { + @apply ml-1 font-semibold; +} diff --git a/app/channels/listener_channel.rb b/app/channels/listener_channel.rb new file mode 100644 index 0000000..055e4d4 --- /dev/null +++ b/app/channels/listener_channel.rb @@ -0,0 +1,31 @@ +class ListenerChannel < ApplicationCable::Channel + def subscribed + return unless station.live? + + stream_from channel_name + station.change_listeners_count_by(1) + broadcast_listeners_count + end + + def unsubscribed + return unless station.live? + + stop_all_streams + station.change_listeners_count_by(-1) + broadcast_listeners_count + end + + private + + def broadcast_listeners_count + ActionCable.server.broadcast channel_name, { listeners: station.current_listeners_count } + end + + def channel_name + @_channel_name ||= "listeners-#{station.id}" + end + + def station + @_station ||= LiveStation.find(params[:station]) + end +end diff --git a/app/controllers/live_stations_controller.rb b/app/controllers/live_stations_controller.rb index ecaca98..17f537d 100644 --- a/app/controllers/live_stations_controller.rb +++ b/app/controllers/live_stations_controller.rb @@ -43,6 +43,7 @@ def stop station = current_user.live_station station.update!(live: false) + station.reset_listeners_count Turbo::StreamsChannel.broadcast_update_to station, target: :player, content: "" diff --git a/app/javascript/application.js b/app/javascript/application.js index 4fdc39a..d5a06e1 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -16,3 +16,4 @@ const cable = createCable({ }); start(cable); +import "channels" diff --git a/app/javascript/channels/consumer.js b/app/javascript/channels/consumer.js new file mode 100644 index 0000000..8ec3aad --- /dev/null +++ b/app/javascript/channels/consumer.js @@ -0,0 +1,6 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command. + +import { createConsumer } from "@rails/actioncable" + +export default createConsumer() diff --git a/app/javascript/channels/index.js b/app/javascript/channels/index.js new file mode 100644 index 0000000..e69de29 diff --git a/app/javascript/controllers/player_controller.js b/app/javascript/controllers/player_controller.js index 6d8e4ff..d738fb3 100644 --- a/app/javascript/controllers/player_controller.js +++ b/app/javascript/controllers/player_controller.js @@ -1,6 +1,7 @@ import { Controller } from "@hotwired/stimulus"; import FakeAudio from "fake_audio"; import { FetchRequest } from "@rails/request.js"; +import consumer from "../channels/consumer" function secondsToDuration(num) { let mins = Math.floor(num / 60); @@ -12,9 +13,9 @@ function secondsToDuration(num) { // Connects to data-controller="player" export default class extends Controller { - static targets = ["progress", "time"]; + static targets = ["progress", "time", "listeners"]; static outlets = ["track"]; - static values = { duration: Number, track: String, nextTrackUrl: String }; + static values = { duration: Number, track: String, nextTrackUrl: String, station: Number }; static classes = ["playing"]; initialize() { @@ -49,6 +50,22 @@ export default class extends Controller { if (this.playing) { this.play(); } + + if (this.hasStationValue) { + this.subscribeListenersCount(); + } + } + + subscribeListenersCount() { + this.channel = consumer.subscriptions.create( + { channel: "ListenerChannel", station: 1 }, + { station: this.stationValue, + received: (data) => { + if (this.hasListenersTarget) { + this.listenersTarget.textContent = data.listeners; + } + } + }); } disconnect() { diff --git a/app/models/live_station.rb b/app/models/live_station.rb index 6535f13..03cfcec 100644 --- a/app/models/live_station.rb +++ b/app/models/live_station.rb @@ -35,4 +35,22 @@ def play_next end def current_track = tracks.first + + def reset_listeners_count() + litecache.set(cache_key, 0) + end + + def change_listeners_count_by(delta) + listeners = current_listeners_count + delta + listeners = 0 if listeners.negative? + litecache.set(cache_key, listeners) + end + + def current_listeners_count + litecache.get(cache_key).to_i + end + + def litecache + @_litecache ||= Litecache.new + end end diff --git a/app/views/icons/_listeners.html b/app/views/icons/_listeners.html new file mode 100644 index 0000000..12daedb --- /dev/null +++ b/app/views/icons/_listeners.html @@ -0,0 +1,3 @@ + + + diff --git a/app/views/player/_player.html.erb b/app/views/player/_player.html.erb index 6d47d6a..7afa999 100644 --- a/app/views/player/_player.html.erb +++ b/app/views/player/_player.html.erb @@ -3,6 +3,7 @@ data-controller="player" data-player-duration-value="<%= track&.duration %>" data-player-track-value="<%= track&.id %>" + data-player-station-value="<%= station&.id %>" data-player-playing-class="player-playing" data-player-next-track-url-value="<%= live ? play_next_live_station_path : (!station && play_next_track_path(track))%>" data-player-track-outlet=".track" @@ -30,6 +31,12 @@ <%= render "icons/signal" %> <%= link_to "Live!", live_station_path, class: "player--title ml-2" %> + + + <%= render "icons/listeners" %> + + <%= station.current_listeners_count %> + <% else %>
diff --git a/config/importmap.rb b/config/importmap.rb index ca7fc2a..08424a8 100644 --- a/config/importmap.rb +++ b/config/importmap.rb @@ -16,3 +16,5 @@ pin "nanoevents", to: "https://ga.jspm.io/npm:nanoevents@7.0.1/index.js" pin "@anycable/web", to: "https://ga.jspm.io/npm:@anycable/web@0.7.0/index.js" pin "@turbo-boost/commands", to: "https://ga.jspm.io/npm:@turbo-boost/commands@0.0.11/app/assets/builds/@turbo-boost/commands.js" +pin "@rails/actioncable", to: "actioncable.esm.js" +pin_all_from "app/javascript/channels", under: "channels"