Merge pull request #46 from akhilrex/websockets

pull/47/head
Akhil Gupta 4 years ago committed by GitHub
commit 0f802b3553
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -46,6 +46,12 @@
display: block;
}
}
.button-enqueue{
display: none;
}
body.playerExists .button-enqueue{
display: inline-block;
}
</style>
</head>
<body>
@ -123,6 +129,12 @@
title="Play Episode"
><i class="fas fa-play"></i
></a>
<a
class="button button-enqueue"
onclick="enqueueEpisode('{{.ID}}')"
title="Add Episode to existing player playlist"
><i class="fas fa-plus"></i
></a>
{{else}} {{if not $setting.AutoDownload}}
<a
@ -257,5 +269,26 @@
return false;
}
</script>
<script>
const socket= getWebsocketConnection(function(event){
const message= getWebsocketMessage("Register","Home")
socket.send(message);
},function(x){
const msg= JSON.parse(x.data)
if(msg.messageType=="NoPlayer"){
document.body.classList.remove("playerExists")
}
if(msg.messageType=="PlayerExists"){
document.body.classList.add("playerExists")
}
});
function enqueueEpisode(id){
if(!socket){
return
}
socket.send(getWebsocketMessage("Enqueue",`{"itemId":"${id}"}`))
}
</script>
</body>
</html>

@ -175,6 +175,15 @@
>
<i class="fas fa-play"></i>
</button>
<button
class="button"
title="Add all downloaded episodes to existing player playlist"
v-if="podcast.DownloadedEpisodesCount>0 && playerExists"
@click="enquePodcast(podcast.ID)"
>
<i class="fas fa-plus"></i>
</button>
</div>
</div>
</div>
@ -227,13 +236,31 @@
}
},
created(){
const self=this;
this.socket= getWebsocketConnection(function(event){
const message= getWebsocketMessage("Register","Home")
self.socket.send(message);
},function(x){
const msg= JSON.parse(x.data)
if(msg.messageType=="NoPlayer"){
self.playerExists=false;
}
if(msg.messageType=="PlayerExists"){
self.playerExists=true;
}
});
},
methods:{
removePodcast(id) {
const index= this.podcasts.findIndex(x=>x.ID===id);
this.podcasts.splice(index,1);
},
enquePodcast(id){
if(!this.playerExists){
return
}
this.socket.send(getWebsocketMessage("Enqueue",`{"podcastId":"${id}"}`))
},
sortPodcasts(order){
let compareFunction;
switch(order){
@ -353,6 +380,8 @@
},
},
data: {
socket:null,
playerExists:false,
isMobile:false,
layoutOptions:["list","grid"],
layout:"grid",
@ -527,5 +556,9 @@
return false;
}
</script>
<script>
</script>
</body>
</html>

@ -656,6 +656,7 @@ div#large-visualization{
{{template "navbar" .}}
<br />{{$setting := .setting}}
<div id="app">
<div id="blue-playlist-container">
<!-- Amplitude Player -->
@ -721,8 +722,8 @@ div#large-visualization{
<!-- Right Side Player -->
<div id="amplitude-right">
{{range $index,$item:=.podcastItems}}
<div class="song amplitude-song-container amplitude-play-pause" data-amplitude-song-index="{{ $index}}" title="{{.Title}}">
<div v-for="(item,index) in allItems" class="song amplitude-song-container amplitude-play-pause" :data-amplitude-song-index="index" :title="item.Title">
<div class="song-now-playing-icon-container">
<div class="play-button-container">
@ -730,21 +731,21 @@ div#large-visualization{
<img class="now-playing" src="/webassets//now-playing.svg"/>
</div>
<div class="song-meta-data">
<span class="song-title">{{.Title}}</span>
<span class="song-date">{{formatDate .PubDate}}</span>
<span class="song-artist">{{.Podcast.Title}}</span>
<span class="song-title">${item.Title}</span>
<span class="song-date">${getFormattedLastEpisodeDate(item)}</span>
<span class="song-artist">${item.Podcast.Title}</span>
</div>
<span class="song-duration">{{ formatDuration .Duration}}</span>
<span class="song-duration">${ formatDuration(item.Duration)}</span>
</div>
{{end}}
</div>
<!-- End Right Side Player -->
</div>
<!-- End Amplitdue Player -->
</div>
</div>
</div>
@ -752,88 +753,139 @@ div#large-visualization{
{{template "scripts"}}
<script src="/webassets/amplitude.min.js"></script>
<style>
.song {
background-color: inherit;
}
.song:not(.amplitude-active-song-container):hover .play-button-container{
display: block!important;;
}
.song:hover{
background-color:#00A0FF ;
}
</style>
<script>
var app = new Vue({
delimiters: ["${", "}"],
el: "#app",
methods:{
getSongsFromItems(items){
return items.map(x=>{
return {
id:x.ID,
name:x.Title,
url:x.DownloadPath,
cover_art_url:x.Image,
artist:x.Podcast.Title,
album: new Date(x.PubDate.substr(0,10)).toDateString()
}
});
},
getFormattedLastEpisodeDate(item){
let dt=new Date(Date.parse(item.PubDate.substr(0,10)));
return dt.toDateString()
},
formatDuration(total) {
if (total <= 0) {
return ""
}
mins = total / 60
mins=Math.floor(mins)
secs = total % 60
hrs = 0
if (mins >= 60) {
hrs = mins / 60
mins = mins % 60
}
hrs=Math.floor(hrs)
if (hrs > 0) {
return `${hrs}:${mins}:${secs}`
}
return `${mins}:${secs}`
},
},
computed:{
songs(){
return this.getSongsFromItems(this.allItems)
}
},
updated(){
Amplitude.bindNewElements();
},
created(){
const self=this;
this.socket= getWebsocketConnection(function(event){
const message= getWebsocketMessage("RegisterPlayer","")
self.socket.send(message);
},x=>{
if(!x.data){
return
}
payload= JSON.parse(x.data);
if(payload.messageType=="Enqueue"){
newItems=JSON.parse(payload.payload);
newItems.forEach(x=>{this.allItems.push(x)});
newSongs=self.getSongsFromItems(newItems);
newSongs.forEach(x=>{Amplitude.addSong(x)});
}
});
},
mounted(){
Amplitude.init({
"songs": this.songs,
"start_song":0,
"autoplay": true,
"callbacks": {
'play': function(){
document.getElementById('album-art').style.visibility = 'hidden';
document.getElementById('large-visualization').style.visibility = 'visible';
},
'pause': function(){
document.getElementById('album-art').style.visibility = 'visible';
document.getElementById('large-visualization').style.visibility = 'hidden';
},
'initialized':function(){
const urlParams = new URLSearchParams(window.location.search);
const itemId = urlParams.get('itemId');
const podcastId = urlParams.get('podcastId');
if(itemId||podcastId){
Amplitude.play();
}
},
'ended':function(){
console.log(Amplitude.getActiveSongMetadata());
}
},
waveforms: {
sample_rate: 50
}
});
},
data:{
socket:null,
allItems: {{ .podcastItems }},
}
});
let songElements = document.getElementsByClassName('song');
for( var i = 0; i < songElements.length; i++ ){
/*
Ensure that on mouseover, CSS styles don't get messed up for active songs.
*/
songElements[i].addEventListener('mouseover', function(){
this.style.backgroundColor = '#00A0FF';
if( !this.classList.contains('amplitude-active-song-container') ){
this.querySelectorAll('.play-button-container')[0].style.display = 'block';
//this.querySelectorAll('.play-button-container')[0].style.backgroundColor = 'inherit';
}
//this.querySelectorAll('.song-duration')[0].style.color = '#FFFFFF';
});
/*
Ensure that on mouseout, CSS styles don't get messed up for active songs.
*/
songElements[i].addEventListener('mouseout', function(){
this.style.backgroundColor = 'inherit';
// this.querySelectorAll('.song-meta-data .song-title')[0].style.color = '#272726';
// this.querySelectorAll('.song-meta-data .song-artist')[0].style.color = '#607D8B';
this.querySelectorAll('.play-button-container')[0].style.display = 'none';
//this.querySelectorAll('.song-duration')[0].style.color = '#607D8B';
});
/*
Show and hide the play button container on the song when the song is clicked.
*/
songElements[i].addEventListener('click', function(){
this.querySelectorAll('.play-button-container')[0].style.display = 'none';
});
}
/*
Initializes AmplitudeJS
*/
let items= {{ .podcastItems }}
songs= items.map(x=>{
return {
name:x.Title,
url:x.DownloadPath,
cover_art_url:x.Image,
artist:x.Podcast.Title,
album: new Date(x.PubDate.substr(0,10)).toDateString()
}
});
Amplitude.init({
"songs": songs,
"start_song":0,
"autoplay": true,
"callbacks": {
'play': function(){
document.getElementById('album-art').style.visibility = 'hidden';
document.getElementById('large-visualization').style.visibility = 'visible';
},
'pause': function(){
document.getElementById('album-art').style.visibility = 'visible';
document.getElementById('large-visualization').style.visibility = 'hidden';
},
'initialized':function(){
const urlParams = new URLSearchParams(window.location.search);
const itemId = urlParams.get('itemId');
const podcastId = urlParams.get('podcastId');
if(itemId||podcastId){
Amplitude.play();
}
}
},
waveforms: {
sample_rate: 50
}
});
</script>
</body>
</html>

@ -5,62 +5,103 @@
<script>
Vue.use(Toasted);
String.prototype.capitalize = function() {
String.prototype.capitalize = function () {
return this.charAt(0).toUpperCase() + this.slice(1);
}
};
const limit = 300;
function checkUseMore(){
let elements = document.getElementsByClassName("useMore");
for (let index = 0; index < elements.length; index++) {
const element = elements[index];
let display = element.style.display;
let originalText = element.textContent;
if (originalText.length <= limit) {
continue;
}
let newText = originalText.substr(0, limit);
let newElement = document.createElement(element.tagName);
newElement.textContent = newText;
newElement.classList.add("short-version");
element.classList.add("long-version");
let more = document.createElement("a");
more.textContent = " show more";
more.style.cursor = "pointer";
more.onclick = function () {
element.style.display = display;
newElement.style.display = "none";
};
let less = document.createElement("a");
less.textContent = " show less";
less.style.cursor = "pointer";
less.onclick = function () {
function checkUseMore() {
let elements = document.getElementsByClassName("useMore");
for (let index = 0; index < elements.length; index++) {
const element = elements[index];
let display = element.style.display;
let originalText = element.textContent;
if (originalText.length <= limit) {
continue;
}
let newText = originalText.substr(0, limit);
let newElement = document.createElement(element.tagName);
newElement.textContent = newText;
newElement.classList.add("short-version");
element.classList.add("long-version");
let more = document.createElement("a");
more.textContent = " show more";
more.style.cursor = "pointer";
more.onclick = function () {
element.style.display = display;
newElement.style.display = "none";
};
let less = document.createElement("a");
less.textContent = " show less";
less.style.cursor = "pointer";
less.onclick = function () {
element.style.display = "none";
newElement.style.display = display;
};
newElement.appendChild(more);
element.appendChild(less);
element.parentNode.insertBefore(newElement, element.nextSibling);
element.style.display = "none";
newElement.style.display = display;
};
}
}
checkUseMore();
newElement.appendChild(more);
element.appendChild(less);
function openPlayer(itemId, podcastId) {
let url = "/player?";
if (itemId) {
url += "&itemId=" + itemId;
}
if (podcastId) {
url += "&podcastId=" + podcastId;
}
const player = window.open(url, "podgrab_player");
}
element.parentNode.insertBefore(newElement, element.nextSibling);
element.style.display = "none";
function getIdentifier() {
if(localStorage){
if (localStorage.identifier) {
return localStorage?.identifier;
}
var id = +new Date();
localStorage.identifier = id;
return id;
}
}
}
checkUseMore();
function openPlayer(itemId,podcastId){
let url="/player?";
if(itemId){
url+="&itemId="+itemId
function getWebsocketMessage(messageType, payload){
return JSON.stringify({
identifier:getIdentifier(),
messageType,
payload
});
}
if(podcastId){
url+="&podcastId="+podcastId
function getWebsocketConnection(onOpen,onMessage){
// Create WebSocket connection.
const socket = new WebSocket('ws://'+window.location.host+'/ws');
socket.addEventListener('open', onOpen);
// Listen for messages
socket.addEventListener('message',onMessage);
return socket;
// Connection opened
// socket.addEventListener('open', function (event) {
// const message= getWebsocketMessage("register","home")
// socket.send(message);
// });
// // Listen for messages
// socket.addEventListener('message', function (event) {
// console.log('Message from server ', event.data);
// });
}
const player= window.open(url,"podgrab_player");
}
</script>
{{end}}

@ -94,6 +94,23 @@ func PodcastPage(c *gin.Context) {
}
func getItemsToPlay(itemId, podcastId string) []db.PodcastItem {
var items []db.PodcastItem
if itemId != "" {
toAdd := service.GetPodcastItemById(itemId)
items = append(items, *toAdd)
} else if podcastId != "" {
pod := service.GetPodcastById(podcastId)
for _, item := range pod.PodcastItems {
if item.DownloadStatus == db.Downloaded {
items = append(items, item)
}
}
}
return items
}
func PlayerPage(c *gin.Context) {
itemId, hasItemId := c.GetQuery("itemId")

@ -0,0 +1,148 @@
package controllers
import (
"encoding/json"
"fmt"
"net/http"
"github.com/gorilla/websocket"
)
type EnqueuePayload struct {
ItemId string `json:"itemId"`
PodcastId string `json:"podcastId"`
}
var wsupgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
var activePlayers = make(map[*websocket.Conn]string)
var allConnections = make(map[*websocket.Conn]string)
var broadcast = make(chan Message) // broadcast channel
type Message struct {
Identifier string `json:"identifier"`
MessageType string `json:"messageType"`
Payload string `json:"payload"`
Connection *websocket.Conn `json:"-"`
}
func Wshandler(w http.ResponseWriter, r *http.Request) {
conn, err := wsupgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println("Failed to set websocket upgrade: %+v", err)
return
}
defer conn.Close()
for {
var mess Message
err := conn.ReadJSON(&mess)
if err != nil {
// fmt.Println("Socket Error")
//fmt.Println(err.Error())
isPlayer := activePlayers[conn] != ""
if isPlayer {
delete(activePlayers, conn)
broadcast <- Message{
MessageType: "PlayerRemoved",
Identifier: mess.Identifier,
}
}
delete(allConnections, conn)
break
}
mess.Connection = conn
allConnections[conn] = mess.Identifier
broadcast <- mess
// conn.WriteJSON(mess)
}
}
func HandleWebsocketMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
//fmt.Println(msg)
switch msg.MessageType {
case "RegisterPlayer":
activePlayers[msg.Connection] = msg.Identifier
for connection, _ := range allConnections {
connection.WriteJSON(Message{
Identifier: msg.Identifier,
MessageType: "PlayerExists",
})
}
fmt.Println("Player Registered")
case "PlayerRemoved":
for connection, _ := range allConnections {
connection.WriteJSON(Message{
Identifier: msg.Identifier,
MessageType: "NoPlayer",
})
}
fmt.Println("Player Registered")
case "Enqueue":
var payload EnqueuePayload
fmt.Println(msg.Payload)
err := json.Unmarshal([]byte(msg.Payload), &payload)
if err == nil {
items := getItemsToPlay(payload.ItemId, payload.PodcastId)
var player *websocket.Conn
for connection, id := range activePlayers {
if msg.Identifier == id {
player = connection
break
}
}
if player != nil {
payloadStr, err := json.Marshal(items)
if err == nil {
player.WriteJSON(Message{
Identifier: msg.Identifier,
MessageType: "Enqueue",
Payload: string(payloadStr),
})
}
}
} else {
fmt.Println(err.Error())
}
case "Register":
var player *websocket.Conn
for connection, id := range activePlayers {
if msg.Identifier == id {
player = connection
break
}
}
if player == nil {
fmt.Println("Player Not Exists")
msg.Connection.WriteJSON(Message{
Identifier: msg.Identifier,
MessageType: "NoPlayer",
})
} else {
msg.Connection.WriteJSON(Message{
Identifier: msg.Identifier,
MessageType: "PlayerExists",
})
}
}
// Send it out to every client that is currently connected
// for client := range clients {
// err := client.WriteJSON(msg)
// if err != nil {
// log.Printf("error: %v", err)
// client.Close()
// delete(clients, client)
// }
// }
}
}

@ -7,6 +7,7 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.6.3
github.com/gobeam/stringy v0.0.0-20200717095810-8a3637503f62
github.com/gorilla/websocket v1.4.2
github.com/grokify/html-strip-tags-go v0.0.0-20200923094847-079d207a09f1
github.com/jasonlvhit/gocron v0.0.1
github.com/joho/godotenv v1.3.0

@ -42,6 +42,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.0-20200923094847-079d207a09f1 h1:ETqBvCd8SQaNCb0TwQ5A+IlkecGuwjW1EUTxK9if+UE=
github.com/grokify/html-strip-tags-go v0.0.0-20200923094847-079d207a09f1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=

@ -133,6 +133,11 @@ func main() {
router.GET("/opml", controllers.GetOmpl)
router.GET("/player", controllers.PlayerPage)
r.GET("/ws", func(c *gin.Context) {
controllers.Wshandler(c.Writer, c.Request)
})
go controllers.HandleWebsocketMessages()
go assetEnv()
go intiCron()
@ -144,6 +149,7 @@ func setupSettings() gin.HandlerFunc {
setting := db.GetOrCreateSetting()
c.Set("setting", setting)
c.Next()
}
}

Loading…
Cancel
Save