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; display: block;
} }
} }
.button-enqueue{
display: none;
}
body.playerExists .button-enqueue{
display: inline-block;
}
</style> </style>
</head> </head>
<body> <body>
@ -123,6 +129,12 @@
title="Play Episode" title="Play Episode"
><i class="fas fa-play"></i ><i class="fas fa-play"></i
></a> ></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}} {{else}} {{if not $setting.AutoDownload}}
<a <a
@ -257,5 +269,26 @@
return false; return false;
} }
</script> </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> </body>
</html> </html>

@ -175,6 +175,15 @@
> >
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</button> </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> </div>
</div> </div>
@ -227,13 +236,31 @@
} }
}, },
created(){ 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:{ methods:{
removePodcast(id) { removePodcast(id) {
const index= this.podcasts.findIndex(x=>x.ID===id); const index= this.podcasts.findIndex(x=>x.ID===id);
this.podcasts.splice(index,1); this.podcasts.splice(index,1);
}, },
enquePodcast(id){
if(!this.playerExists){
return
}
this.socket.send(getWebsocketMessage("Enqueue",`{"podcastId":"${id}"}`))
},
sortPodcasts(order){ sortPodcasts(order){
let compareFunction; let compareFunction;
switch(order){ switch(order){
@ -353,6 +380,8 @@
}, },
}, },
data: { data: {
socket:null,
playerExists:false,
isMobile:false, isMobile:false,
layoutOptions:["list","grid"], layoutOptions:["list","grid"],
layout:"grid", layout:"grid",
@ -527,5 +556,9 @@
return false; return false;
} }
</script> </script>
<script>
</script>
</body> </body>
</html> </html>

@ -656,6 +656,7 @@ div#large-visualization{
{{template "navbar" .}} {{template "navbar" .}}
<br />{{$setting := .setting}} <br />{{$setting := .setting}}
<div id="app">
<div id="blue-playlist-container"> <div id="blue-playlist-container">
<!-- Amplitude Player --> <!-- Amplitude Player -->
@ -721,8 +722,8 @@ div#large-visualization{
<!-- Right Side Player --> <!-- Right Side Player -->
<div id="amplitude-right"> <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="song-now-playing-icon-container">
<div class="play-button-container"> <div class="play-button-container">
@ -730,21 +731,21 @@ div#large-visualization{
<img class="now-playing" src="/webassets//now-playing.svg"/> <img class="now-playing" src="/webassets//now-playing.svg"/>
</div> </div>
<div class="song-meta-data"> <div class="song-meta-data">
<span class="song-title">{{.Title}}</span> <span class="song-title">${item.Title}</span>
<span class="song-date">{{formatDate .PubDate}}</span> <span class="song-date">${getFormattedLastEpisodeDate(item)}</span>
<span class="song-artist">{{.Podcast.Title}}</span> <span class="song-artist">${item.Podcast.Title}</span>
</div> </div>
<span class="song-duration">{{ formatDuration .Duration}}</span> <span class="song-duration">${ formatDuration(item.Duration)}</span>
</div> </div>
{{end}}
</div> </div>
<!-- End Right Side Player --> <!-- End Right Side Player -->
</div> </div>
<!-- End Amplitdue Player --> <!-- End Amplitdue Player -->
</div> </div>
</div>
</div> </div>
@ -752,54 +753,31 @@ div#large-visualization{
{{template "scripts"}} {{template "scripts"}}
<script src="/webassets/amplitude.min.js"></script> <script src="/webassets/amplitude.min.js"></script>
<script>
let songElements = document.getElementsByClassName('song');
for( var i = 0; i < songElements.length; i++ ){ <style>
/* .song {
Ensure that on mouseover, CSS styles don't get messed up for active songs. background-color: inherit;
*/
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'; .song:not(.amplitude-active-song-container):hover .play-button-container{
//this.querySelectorAll('.play-button-container')[0].style.backgroundColor = 'inherit'; display: block!important;;
} }
//this.querySelectorAll('.song-duration')[0].style.color = '#FFFFFF'; .song:hover{
}); background-color:#00A0FF ;
/*
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';
});
} }
</style>
<script>
/*
Initializes AmplitudeJS
*/
let items= {{ .podcastItems }}
songs= items.map(x=>{ var app = new Vue({
delimiters: ["${", "}"],
el: "#app",
methods:{
getSongsFromItems(items){
return items.map(x=>{
return { return {
id:x.ID,
name:x.Title, name:x.Title,
url:x.DownloadPath, url:x.DownloadPath,
cover_art_url:x.Image, cover_art_url:x.Image,
@ -807,8 +785,61 @@ songs= items.map(x=>{
album: new Date(x.PubDate.substr(0,10)).toDateString() 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({ Amplitude.init({
"songs": songs, "songs": this.songs,
"start_song":0, "start_song":0,
"autoplay": true, "autoplay": true,
"callbacks": { "callbacks": {
@ -828,12 +859,33 @@ Amplitude.init({
if(itemId||podcastId){ if(itemId||podcastId){
Amplitude.play(); Amplitude.play();
} }
},
'ended':function(){
console.log(Amplitude.getActiveSongMetadata());
} }
}, },
waveforms: { waveforms: {
sample_rate: 50 sample_rate: 50
} }
}); });
},
data:{
socket:null,
allItems: {{ .podcastItems }},
}
});
let songElements = document.getElementsByClassName('song');
for( var i = 0; i < songElements.length; i++ ){
songElements[i].addEventListener('click', function(){
this.querySelectorAll('.play-button-container')[0].style.display = 'none';
});
}
</script> </script>
</body> </body>
</html> </html>

@ -7,7 +7,7 @@
Vue.use(Toasted); Vue.use(Toasted);
String.prototype.capitalize = function () { String.prototype.capitalize = function () {
return this.charAt(0).toUpperCase() + this.slice(1); return this.charAt(0).toUpperCase() + this.slice(1);
} };
const limit = 300; const limit = 300;
function checkUseMore() { function checkUseMore() {
let elements = document.getElementsByClassName("useMore"); let elements = document.getElementsByClassName("useMore");
@ -55,12 +55,53 @@ checkUseMore();
function openPlayer(itemId, podcastId) { function openPlayer(itemId, podcastId) {
let url = "/player?"; let url = "/player?";
if (itemId) { if (itemId) {
url+="&itemId="+itemId url += "&itemId=" + itemId;
} }
if (podcastId) { if (podcastId) {
url+="&podcastId="+podcastId url += "&podcastId=" + podcastId;
} }
const player = window.open(url, "podgrab_player"); const player = window.open(url, "podgrab_player");
} }
function getIdentifier() {
if(localStorage){
if (localStorage.identifier) {
return localStorage?.identifier;
}
var id = +new Date();
localStorage.identifier = id;
return id;
}
}
function getWebsocketMessage(messageType, payload){
return JSON.stringify({
identifier:getIdentifier(),
messageType,
payload
});
}
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);
// });
}
</script> </script>
{{end}} {{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) { func PlayerPage(c *gin.Context) {
itemId, hasItemId := c.GetQuery("itemId") 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/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.6.3 github.com/gin-gonic/gin v1.6.3
github.com/gobeam/stringy v0.0.0-20200717095810-8a3637503f62 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/grokify/html-strip-tags-go v0.0.0-20200923094847-079d207a09f1
github.com/jasonlvhit/gocron v0.0.1 github.com/jasonlvhit/gocron v0.0.1
github.com/joho/godotenv v1.3.0 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/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 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= 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 h1:ETqBvCd8SQaNCb0TwQ5A+IlkecGuwjW1EUTxK9if+UE=
github.com/grokify/html-strip-tags-go v0.0.0-20200923094847-079d207a09f1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78= 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= 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("/opml", controllers.GetOmpl)
router.GET("/player", controllers.PlayerPage) router.GET("/player", controllers.PlayerPage)
r.GET("/ws", func(c *gin.Context) {
controllers.Wshandler(c.Writer, c.Request)
})
go controllers.HandleWebsocketMessages()
go assetEnv() go assetEnv()
go intiCron() go intiCron()
@ -144,6 +149,7 @@ func setupSettings() gin.HandlerFunc {
setting := db.GetOrCreateSetting() setting := db.GetOrCreateSetting()
c.Set("setting", setting) c.Set("setting", setting)
c.Next() c.Next()
} }
} }

Loading…
Cancel
Save