add global player

This commit is contained in:
Stepan Fedyanin 2024-06-11 17:49:36 +05:00
parent b9be44bdde
commit dd80e653ae
16 changed files with 233 additions and 129 deletions

View File

@ -1,15 +1,15 @@
<template>
<div class="app">
<AppHeader :showAuthentication="showAuthentication" @shopAuthentication="changeShowAuthentication"/>
<AppHeader/>
<div class="app__block">
<Page404 v-if="showErrorPage404"/>
<Page500 v-if="showErrorPage500"/>
<routerView v-else/>
</div>
<Player @shopAuthentication="changeShowAuthentication"/>
<Player/>
<AppFooter/>
</div>
<Authentication :showModal="showAuthentication" @hideModal="changeShowAuthentication"/>
<Authentication/>
</template>
<script>
@ -33,7 +33,6 @@ export default {
},
data() {
return {
showAuthentication: false,
showSidebarBlock: false,
};
},
@ -56,9 +55,6 @@ export default {
mounted() {
},
methods: {
changeShowAuthentication(value) {
this.showAuthentication = value;
}
}
};
</script>

View File

@ -1,5 +1,5 @@
.footer{
padding: 0 0 var(--space-between-block);
padding-bottom: var(--space-between-block);
width: 100%;
&__top{
padding-bottom: 80px;

View File

@ -29,6 +29,10 @@
&__content{
max-width: var(--container);
margin: 0 auto;
padding: 0 50px;
@mixin responsive-l {
padding: 0 20px;
}
.m--indentation{
}
}

View File

@ -51,7 +51,12 @@
&__subtitle {
text-align: center;
max-width: 600px;
margin: 0 auto;
@mixin responsive-l {
padding: 0 20px;
}
span {
font-weight: 700;
}
@ -105,26 +110,31 @@
max-height: var(--base-content-size);
background-size: contain;
scale: 0.8;
margin-left: calc(-1920px + 100vw);
margin-left: calc((100vw + -1 * 1920px) / 2);
}
&__description {
margin-top: calc(var(--space-between-sections) / 2);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-bottom: var(--space-between-sections);
@mixin responsive-m {
gap: var(--space-between-block);
}
}
&__info {
width: 60%;
@mixin responsive-m {
width: 100%;
}
&--item {
display: flex;
justify-content: space-between;
color: transparent;
background-clip: text;
background-image: var(--linear-gradient);
&.m--circle {
align-items: end;
@ -141,20 +151,32 @@
font-weight: 400;
color: var(--color-white);
width: 57%;
@mixin responsive-m {
width: 75%;
}
}
}
}
&__content {
max-width: 360px;
@mixin responsive-m {
display: flex;
align-items: flex-start;
gap: calc(var(--space-between-block) / 2);
max-width: none;
}
&--item {
display: flex;
flex-direction: column;
align-items: flex-start;
font-size: 16px;
margin-bottom: 2.5rem;
position: relative;
z-index: 2;
@mixin responsive-m {
width: calc(100% / 3);
}
&:before {
content: '';
height: 50px;

View File

@ -8,6 +8,8 @@
background-image: var(--linear-gradient);
margin: 0.75rem 0;
gap: 1rem;
position: relative;
z-index: 2;
.spinner {
width: 48px;
height: 48px;

View File

@ -5,7 +5,7 @@
gap: 0 var(--space-between-block);
}
&-item{
width: calc(50% - 40px / 2);
width: calc(50% - var(--space-between-block) / 2);
position: relative;
display: flex;
align-items: center;

View File

@ -5,12 +5,12 @@
<img src="@/assets/img/icon/logo.svg" alt="logo"/>
</div>
<ul class="header__menu" :class="showMenu&&'m--active'">
<div class="header__burger m--active m--menu" @click="handlerShowMenu">
<li class="header__burger m--active m--menu" @click="handlerShowMenu">
<span></span>
</div>
<div class="header__logo m--menu" @click="next('home')">
</li>
<li class="header__logo m--menu" @click="next('home')">
<img src="@/assets/img/icon/logo.svg" alt="logo"/>
</div>
</li>
<li
v-for="(item, key) in menu"
:key="key"
@ -38,14 +38,7 @@
<script>
export default {
name: 'app-header',
props:{
showAuthentication:{
type: Boolean,
default(){
return false
}
}
},
props:{},
data(){
return{
menuList:[
@ -117,12 +110,15 @@ export default {
if (item.role==='login' && !this.user?.id) return !this.user?.id
return false
})
}
},
showAuthentication(){
return this.$store.state.showAuthModal
},
},
methods:{
handlerClick(methods){
if (methods==='login'){
this.$emit('shopAuthentication', true)
this.$store.dispatch('setShowAuthModal', true);
}
if (methods==='profile'){
this.next('profile')

View File

@ -6,11 +6,11 @@
content-transition="vfm-fade"
overlay-transition="vfm-fade"
:clickToClose="false"
@click-outside="$emit('hideModal')"
@click-outside="close()"
>
<button
class="button modal__close"
@click="$emit('hideModal')"
@click="close"
>
</button>
<div class="authentication">
@ -40,7 +40,7 @@
outerClass: '$reset',
}"
@submit="submitHandler"
@click-outside="$emit('hideModal')"
@click-outside="close"
>
<FormKitSchema :schema="loginForm"/>
</FormKit>
@ -73,14 +73,6 @@ import {app} from "@/services";
export default {
name: 'authentication',
props: {
showModal: {
type: Boolean,
default() {
return false;
}
},
},
data() {
return {
currentTabsItem: 'login',
@ -145,8 +137,8 @@ export default {
},
computed: {
show(){
return this.showModal;
},
return this.$store.state.showAuthModal
}
},
methods: {
changeTab(tab) {
@ -158,8 +150,8 @@ export default {
this.$store.dispatch('setToken', res);
app.user().then(user=>{
this.$store.dispatch('setUser', user);
this.close();
this.next('profile');
this.$emit('hideModal');
}).catch(err=>{
console.log(err)
})
@ -171,8 +163,8 @@ export default {
this.$store.dispatch('setToken', res);
app.user().then(user=>{
this.$store.dispatch('setUser', user);
this.close();
this.next('profile');
this.$emit('hideModal');
}).catch(err=>{
console.log(err)
})
@ -183,7 +175,7 @@ export default {
},
close() {
this.$emit('hidden', false);
this.$store.dispatch('setShowAuthModal', false);
},
next(name){
this.$router.push({name});

View File

@ -5,7 +5,7 @@
</div>
<div class="player__content">
<div class="player__top">
<button v-if="isPlay" @click="handlerPause" class="button player__btn m--pause">
<button v-if="currentPlay.isPlay" @click="handlerPause" class="button player__btn m--pause">
</button>
<button v-else @click="handlerPlay" class="button player__btn m--play">
</button>
@ -16,8 +16,9 @@
<div class="player__favorites" :class="[isFavorites&&'m--active']" @click="handlerFavorites">
</div>
<div class="player__tools">
{{isLive}}
<FormKit
v-model="isPlayRadio"
v-model="isLive"
type="toggle"
label="Включить мою музыку"
/>
@ -40,46 +41,48 @@
</template>
<script>
import {audio as Player} from "@/services";
import {app} from "@/services";
import {audio as Player, app} from "@/services";
export default {
name: 'player',
data() {
return {
audioUrl: 'http://82.97.242.49:18000/radio.mp3',
isPlay: false,
isFavorites: false,
isPlayRadio: true,
connection: null,
currentPlay: {},
player: {
target: null,
volume: 50,
progress: 100,
live: false,
isLive: false
}
},
computed:{
user() {
return this.$store.state.user;
},
currentPlay(){
return this.$store.state.currentPlay
},
player(){
return this.$store.state.player
}
},
watch:{
'currentPlay': {
immediate: true,
handler() {
console.debug('this.currentPlay.live',!this.currentPlay.live)
this.isLive = !this.currentPlay.live
},
}
},
created() {
this.connectionPlayer();
},
mounted() {
this.initPlayer();
// this.$refs.player.addEventListener('timeupdate', this.updateProgress);
},
computed:{
user() {
return this.$store.state.user;
},
// if (!this.player.target){
this.$store.dispatch('initPlayer');
// }
},
methods: {
initPlayer(){
this.player.target = document.createElement('audio')
this.player.target.src = '';
this.player.target.preload = 'auto';
this.player.target.controls = true
},
connectionPlayer() {
if (this.connection) {
this.connection.removePlay();
@ -92,11 +95,12 @@ export default {
const jsonData = JSON.parse(e.data)
if (jsonData?.pub?.data){
const data = jsonData?.pub?.data;
if (this.currentPlay.live){
if (data.np.station.listen_url!==this.player.target.src){
this.player.target.src = data.np.station.listen_url;
this.player.live = true;
this.$store.dispatch('changePlayer', data.np.station.listen_url);
}
this.$store.dispatch('setCurrentPlay', {...data.np.now_playing.song, live: false});
}
this.currentPlay = data.np.now_playing.song;
}
},
updateProgress(){
@ -104,17 +108,10 @@ export default {
console.log(this.$refs.player.duration)
},
handlerPlay() {
if (this.player.target?.play){
this.player.target.play();
this.isPlay = true;
}
this.$store.dispatch('handlerPlayer', {play: true});
},
handlerPause() {
console.log(this.player.target?.pause)
if (this.player.target.src) {
this.player.target.pause();
this.isPlay = false;
}
this.$store.dispatch('handlerPlayer', {pause: true});
},
handlerFavorites(){
if (this.user?.id){
@ -129,11 +126,10 @@ export default {
},
setVolume(){
this.player.volume = this.player.volume === 0? 50: 0;
this.player.target.volume = this.player.volume / 100;
this.$store.dispatch('handlerPlayer', {volume: 100});
},
changeVolume() {
this.player.target.volume = this.player.volume / 100;
this.$store.dispatch('handlerPlayer', {volume: 100});
},
}

View File

@ -1,5 +1,5 @@
<template>
<div class="song-item" :class="[selectSong===song&&'m--select']" @click="handlerSelectSong">
<div class="song-item" :class="[currentPlay.id===song.id&&'m--select']" @click="handlerSelectSong">
<div class="song-item__selected" :class="[!isPlay&&'m--stop']">
<svg width="33" height="28" viewBox="0 0 33 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path class="" d="M0 15C0 13.6193 1.11929 12.5 2.5 12.5C3.88071 12.5 5 13.6193 5 15V27.5H0V15Z" fill="#7138F4"/>
@ -16,8 +16,8 @@
</button>
</div>
<div class="song-item__info">
<span>Название трека</span>
Исполнитель
<span>{{song.title}}</span>
{{song.artist}}
</div>
<div class="song-item__tools">
<button class="button song-item__btn m--small m--favorites"></button>
@ -37,9 +37,9 @@ export default {
}
},
song:{
type: Number,
type: Object,
default(){
return null
return {}
}
},
playSong:{
@ -62,6 +62,11 @@ export default {
isPlay: this.playSong
}
},
computed:{
currentPlay(){
return this.$store.state.currentPlay
}
},
methods:{
handlerPlay() {
this.isPlay = true;
@ -70,7 +75,7 @@ export default {
this.isPlay = false;
},
handlerSelectSong(){
this.$emit('changeSelect', this.song)
this.$store.dispatch('setCurrentPlay', {...this.song, live: false});
}
}
}

View File

@ -1,12 +1,9 @@
<template>
<div class="song-list">
<SongItem
v-for="i in 10"
:key="i"
:song="i"
:playSong="i===selectSong"
:selectSong="selectSong"
@changeSelect="changeSelectSong"
v-for="song in songList"
:key="song"
:song="song"
/>
</div>
</template>
@ -17,15 +14,15 @@ import SongItem from "@/components/song-item.vue";
export default {
name: 'song-list',
components: {SongItem},
data(){
return{
selectSong: null
props: {
songList: {
type: Array,
default: () => []
}
},
methods:{
changeSelectSong(id){
this.selectSong = id;
}
}
data() {
return {}
},
methods: {}
}
</script>

View File

@ -27,6 +27,34 @@ export default class extends REST {
});
}
static getTeams(station, params) {
return this._get(`radio/teams`, params, {}).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении команды');
});
}
static createFavoriteForUser(params){
return this._post(`radio/song/add_favorite`, {}, params).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static getFavoriteList(params){
return this._get(`radio/song`, {}, params).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static getNews(station, params) {
return this._get(`radio.mp3`, params, {}).then((data) => {
return data;
@ -41,13 +69,7 @@ export default class extends REST {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static getTeams(station, params) {
return this._get(`radio/teams`, params, {}).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении команды');
});
}
static getRubriks(station, params) {
return this._get(`radio.mp3`, params, {}).then((data) => {
return data;
@ -55,11 +77,4 @@ export default class extends REST {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
static createFavoriteForUser(params){
return this._post(`radio/song/create_song`, {}, params).then((data) => {
return data;
}).catch((error) => {
throw new RESTError(error, 'Ошибка при получении плейлистов');
});
}
}

View File

@ -20,7 +20,7 @@ class RESTError extends Error {
//if (this.response && (this.response.status === 401 || this.response.status === 403)) {
if (this.response && (this.response.status === 401)) {
store.dispatch('deathUser');
router.push({ name: 'auth' });
store.dispatch('setShowAuthModal', true);
} else {
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);

View File

@ -2,7 +2,7 @@ import { createStore } from 'vuex'
import VuexPersist from 'vuex-persist';
const vuexPersist = new VuexPersist({
key: 'tugan-v-cirk-ne-hodim',
key: 'it-radio',
//storage: window.localStorage
});
@ -14,8 +14,15 @@ export default createStore({
token: null,
refreshToken: null,
user: null,
showAuthModal: false,
station: {
id: 1
},
currentPlay: {},
player:{
target: null,
volume: 50,
live: false,
}
}
},
@ -32,6 +39,38 @@ export default createStore({
state.token = null;
state.refreshToken = null;
},
setCurrentPlay(state, song){
state.currentPlay = song;
},
setShowAuthModal(state, show){
state.showAuthModal = show
},
setPlayer(state, params){
state.player = {...state.player,...params}
},
initPlayer(state){
state.player.target = document.createElement('audio')
state.player.target.src = '';
state.player.target.preload = 'auto';
state.player.target.controls = true;
console.log(state.player.target)
},
changePlayer(state, params){
state.player.target.src = params;
},
handlerPlayer(state, params){
if (params.pause){
state.currentPlay.isPlay = false;
state.player.target.pause();
}
if (params.play){
state.currentPlay.isPlay = true;
state.player.target.play();
}
if (params.volume){
state.player.target.volume = params.volume;
}
},
},
actions: {
setToken(context, tokens) {
@ -44,5 +83,24 @@ export default createStore({
context.commit('user', {});
context.commit('removeToken');
},
setCurrentPlay(context, song){
context.commit('setCurrentPlay', song);
},
setShowAuthModal(context, show){
context.commit('setShowAuthModal', show);
},
setPlayer(context, params){
context.commit('setPlayer', params);
},
initPlayer(context){
context.commit('initPlayer');
},
handlerPlayer(context, params){
context.commit('handlerPlayer', params);
},
changePlayer(context, params){
context.commit('changePlayer', params);
}
}
});

View File

@ -3,9 +3,9 @@
<div class="home__meaning" ref="targetWrapper">
<div class="app__content">
<p class="text home__subtitle">
Мы цифровое <span>онлайн радио.</span>
Помогаем разобраться в том, что такое <span>IT.</span>
Находимся в <span>Челябинске</span>, но вещаем на весь <span>Мир</span>
Мы цифровое <span>онлайн радио.</span><br/>
Помогаем разобраться в том, что такое <span>IT.</span><br/>
Находимся в <span>Челябинске</span>, но вещаем на весь <span>Мир</span><br/>
</p>
<h1 class="home__title" ref="targetTitle">
IT-Радио <span id="targetTitleSpan">радиостанция про сферу</span> технологий <br/> и развитие <span id="targetTitleSpan">в IT</span>
@ -19,7 +19,7 @@
<div class="home__banner" ref="target">
</div>
</div>
<template v-if="false">
<template v-if="true">
<div class="app__content">
<div class="home__description">

View File

@ -20,7 +20,12 @@
</button>
</div>
<template v-if="currentTabsItem==='music'">
<SongList/>
<template v-if="showLoader">
<div class="loader">
<div class="spinner" /> Загрузка данных
</div>
</template>
<SongList v-else :songList="songList"/>
</template>
</div>
</template>
@ -28,6 +33,7 @@
<script>
import AppBreadcrumbs from "@/components/app-breadcrumbs.vue";
import SongList from "@/components/song-list.vue";
import {app} from "@/services";
export default {
name: 'profile',
@ -49,6 +55,8 @@ export default {
name: 'playlists'
},
],
songList:[],
showLoader:true,
}
},
watch: {
@ -63,7 +71,20 @@ export default {
},
}
},
created() {
this.getSongList();
},
methods: {
getSongList(){
this.showLoader = true;
app.getFavoriteList().then(res=>{
this.showLoader = false;
this.songList = res;
}).catch(err=>{
this.showLoader = false;
console.error(err)
})
},
changeTab(tab) {
this.currentTabsItem = tab;
this.$router.push({name: this.$route.name, hash: `#${tab}`});