kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
				
				
				
			
			
			
			
				environments/review-front-funk-4sh69p/deployments/30
			
			
		
		
							rodzic
							
								
									3862034dbc
								
							
						
					
					
						commit
						1f08475017
					
				|  | @ -0,0 +1 @@ | |||
| Fixed broken audio playback on Chrome and invisible volume control (#390) | ||||
|  | @ -0,0 +1 @@ | |||
| Use Howler to manage audio instead of our own dirty/untested code (#392) | ||||
|  | @ -21,6 +21,7 @@ | |||
|     "axios": "^0.17.1", | ||||
|     "dateformat": "^2.0.0", | ||||
|     "django-channels": "^1.1.6", | ||||
|     "howler": "^2.0.14", | ||||
|     "js-logger": "^1.3.0", | ||||
|     "jwt-decode": "^2.2.0", | ||||
|     "lodash": "^4.17.4", | ||||
|  |  | |||
|  | @ -1,16 +1,15 @@ | |||
| <template> | ||||
|   <div class="ui inverted segment player-wrapper" :style="style"> | ||||
|     <div class="player"> | ||||
|       <keep-alive> | ||||
|         <audio-track | ||||
|           ref="currentAudio" | ||||
|           v-if="renderAudio && currentTrack" | ||||
|           :is-current="true" | ||||
|           :start-time="$store.state.player.currentTime" | ||||
|           :autoplay="$store.state.player.playing" | ||||
|           :track="currentTrack"> | ||||
|         </audio-track> | ||||
|       </keep-alive> | ||||
|       <audio-track | ||||
|         ref="currentAudio" | ||||
|         v-if="currentTrack" | ||||
|         :is-current="true" | ||||
|         :start-time="$store.state.player.currentTime" | ||||
|         :autoplay="$store.state.player.playing" | ||||
|         :key="audioKey" | ||||
|         :track="currentTrack"> | ||||
|       </audio-track> | ||||
|       <div v-if="currentTrack" class="track-area ui unstackable items"> | ||||
|         <div class="ui inverted item"> | ||||
|           <div class="ui tiny image"> | ||||
|  | @ -160,13 +159,13 @@ | |||
| import {mapState, mapGetters, mapActions} from 'vuex' | ||||
| import GlobalEvents from '@/components/utils/global-events' | ||||
| import ColorThief from '@/vendor/color-thief' | ||||
| import {Howl} from 'howler' | ||||
| 
 | ||||
| import AudioTrack from '@/components/audio/Track' | ||||
| import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon' | ||||
| import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon' | ||||
| 
 | ||||
| export default { | ||||
|   name: 'player', | ||||
|   components: { | ||||
|     TrackFavoriteIcon, | ||||
|     TrackPlaylistIcon, | ||||
|  | @ -177,16 +176,28 @@ export default { | |||
|     let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]] | ||||
|     return { | ||||
|       isShuffling: false, | ||||
|       renderAudio: true, | ||||
|       sliderVolume: this.volume, | ||||
|       defaultAmbiantColors: defaultAmbiantColors, | ||||
|       showVolume: false, | ||||
|       ambiantColors: defaultAmbiantColors | ||||
|       ambiantColors: defaultAmbiantColors, | ||||
|       audioKey: String(new Date()), | ||||
|       dummyAudio: null | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     // we trigger the watcher explicitely it does not work otherwise | ||||
|     this.sliderVolume = this.volume | ||||
|     // this is needed to unlock audio playing under some browsers, | ||||
|     // cf https://github.com/goldfire/howler.js#mobilechrome-playback | ||||
|     // but we never actually load those audio files | ||||
|     this.dummyAudio = new Howl({ | ||||
|       preload: false, | ||||
|       autoplay: false, | ||||
|       src: ['noop.webm', 'noop.mp3'] | ||||
|     }) | ||||
|   }, | ||||
|   destroyed () { | ||||
|     this.dummyAudio.unload() | ||||
|   }, | ||||
|   methods: { | ||||
|     ...mapActions({ | ||||
|  | @ -305,21 +316,13 @@ export default { | |||
|   }, | ||||
|   watch: { | ||||
|     currentTrack (newValue) { | ||||
|       if (!this.isShuffling) { | ||||
|         this.audioKey = String(new Date()) | ||||
|       } | ||||
|       if (!newValue || !newValue.album.cover) { | ||||
|         this.ambiantColors = this.defaultAmbiantColors | ||||
|       } | ||||
|     }, | ||||
|     currentIndex (newValue, oldValue) { | ||||
|       if (newValue !== oldValue) { | ||||
|         // why this? to ensure the audio tag is deleted and fully | ||||
|         // rerendered, so we don't have any issues with cached position | ||||
|         // or whatever | ||||
|         this.renderAudio = false | ||||
|         this.$nextTick(() => { | ||||
|           this.renderAudio = true | ||||
|         }) | ||||
|       } | ||||
|     }, | ||||
|     volume (newValue) { | ||||
|       this.sliderVolume = newValue | ||||
|     }, | ||||
|  | @ -385,9 +388,6 @@ export default { | |||
| .volume-control { | ||||
|   position: relative; | ||||
|   width: 12.5% !important; | ||||
|   .icon { | ||||
|     // margin: 0; | ||||
|   } | ||||
|   [type="range"] { | ||||
|     max-width: 70%; | ||||
|     position: absolute; | ||||
|  | @ -395,16 +395,11 @@ export default { | |||
|     left: 25%; | ||||
|     cursor: pointer; | ||||
|   } | ||||
|   input[type=range] { | ||||
|     -webkit-appearance: none; | ||||
|   } | ||||
|   input[type=range]:focus { | ||||
|     outline: none; | ||||
|   } | ||||
|   input[type=range]::-webkit-slider-runnable-track { | ||||
|     cursor: pointer; | ||||
|     background: white; | ||||
|     opacity: 0.3; | ||||
|   } | ||||
|   input[type=range]::-webkit-slider-thumb { | ||||
|     background: white; | ||||
|  | @ -413,10 +408,6 @@ export default { | |||
|     border-radius: 3px; | ||||
|     width: 10px; | ||||
|   } | ||||
|   input[type=range]:focus::-webkit-slider-runnable-track { | ||||
|     background: #white; | ||||
|     opacity: 0.3; | ||||
|   } | ||||
|   input[type=range]::-moz-range-track { | ||||
|     cursor: pointer; | ||||
|     background: white; | ||||
|  | @ -455,7 +446,7 @@ export default { | |||
|     background: white; | ||||
|   } | ||||
|   input[type=range]:focus::-ms-fill-upper { | ||||
|     background: #white; | ||||
|     background: white; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,24 +1,13 @@ | |||
| <template> | ||||
|   <audio | ||||
|     ref="audio" | ||||
|     @error="errored" | ||||
|     @loadeddata="loaded" | ||||
|     @durationchange="updateDuration" | ||||
|     @timeupdate="updateProgressThrottled" | ||||
|     @ended="ended" | ||||
|     preload> | ||||
|     <source | ||||
|       @error="sourceErrored" | ||||
|       v-for="src in srcs" | ||||
|       :src="src.url" | ||||
|       :type="src.type"> | ||||
|   </audio> | ||||
|   <i /> | ||||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import {mapState} from 'vuex' | ||||
| import url from '@/utils/url' | ||||
| import _ from 'lodash' | ||||
| import url from '@/utils/url' | ||||
| import {Howl} from 'howler' | ||||
| 
 | ||||
| // import logger from '@/logging' | ||||
| 
 | ||||
| export default { | ||||
|  | @ -30,11 +19,44 @@ export default { | |||
|   }, | ||||
|   data () { | ||||
|     return { | ||||
|       realTrack: this.track, | ||||
|       sourceErrors: 0, | ||||
|       isUpdatingTime: false | ||||
|       sound: null, | ||||
|       isUpdatingTime: false, | ||||
|       progressInterval: null | ||||
|     } | ||||
|   }, | ||||
|   mounted () { | ||||
|     let self = this | ||||
|     this.sound = new Howl({ | ||||
|       src: this.srcs.map((s) => { return s.url }), | ||||
|       autoplay: false, | ||||
|       loop: false, | ||||
|       html5: true, | ||||
|       preload: true, | ||||
|       volume: this.volume, | ||||
|       onend: function () { | ||||
|         self.ended() | ||||
|       }, | ||||
|       onunlock: function () { | ||||
|         if (this.$store.state.player.playing) { | ||||
|           self.sound.play() | ||||
|         } | ||||
|       }, | ||||
|       onload: function () { | ||||
|         self.$store.commit('player/resetErrorCount') | ||||
|         self.$store.commit('player/duration', self.sound.duration()) | ||||
|       } | ||||
|     }) | ||||
|     if (this.autoplay) { | ||||
|       this.sound.play() | ||||
|       this.$store.commit('player/playing', true) | ||||
|       this.observeProgress(true) | ||||
|     } | ||||
|   }, | ||||
|   destroyed () { | ||||
|     this.observeProgress(false) | ||||
|     this.sound.unload() | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapState({ | ||||
|       playing: state => state.player.playing, | ||||
|  | @ -44,7 +66,7 @@ export default { | |||
|       looping: state => state.player.looping | ||||
|     }), | ||||
|     srcs: function () { | ||||
|       let file = this.realTrack.files[0] | ||||
|       let file = this.track.files[0] | ||||
|       if (!file) { | ||||
|         this.$store.dispatch('player/trackErrored') | ||||
|         return [] | ||||
|  | @ -68,90 +90,58 @@ export default { | |||
|     } | ||||
|   }, | ||||
|   methods: { | ||||
|     errored: function () { | ||||
|       let self = this | ||||
|       setTimeout( | ||||
|         () => { self.$store.dispatch('player/trackErrored') } | ||||
|       , 1000) | ||||
|     }, | ||||
|     sourceErrored: function () { | ||||
|       this.sourceErrors += 1 | ||||
|       if (this.sourceErrors >= this.srcs.length) { | ||||
|         // all sources failed | ||||
|         this.errored() | ||||
|       } | ||||
|     }, | ||||
|     updateDuration: function (e) { | ||||
|       if (!this.$refs.audio) { | ||||
|         return | ||||
|       } | ||||
|       this.$store.commit('player/duration', this.$refs.audio.duration) | ||||
|     }, | ||||
|     loaded: function () { | ||||
|       if (!this.$refs.audio) { | ||||
|         return | ||||
|       } | ||||
|       this.$refs.audio.volume = this.volume | ||||
|       this.$store.commit('player/resetErrorCount') | ||||
|       if (this.isCurrent) { | ||||
|         this.$store.commit('player/duration', this.$refs.audio.duration) | ||||
|         if (this.startTime) { | ||||
|           this.setCurrentTime(this.startTime) | ||||
|         } | ||||
|         if (this.autoplay) { | ||||
|           this.$store.commit('player/playing', true) | ||||
|           this.$refs.audio.play() | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     updateProgress: function () { | ||||
|       this.isUpdatingTime = true | ||||
|       if (this.$refs.audio) { | ||||
|         this.$store.dispatch('player/updateProgress', this.$refs.audio.currentTime) | ||||
|       if (this.sound && this.sound.state() === 'loaded') { | ||||
|         this.$store.dispatch('player/updateProgress', this.sound.seek()) | ||||
|       } | ||||
|     }, | ||||
|     ended: function () { | ||||
|       let onlyTrack = this.$store.state.queue.tracks.length === 1 | ||||
|       if (this.looping === 1 || (onlyTrack && this.looping === 2)) { | ||||
|         this.setCurrentTime(0) | ||||
|         this.$refs.audio.play() | ||||
|     observeProgress: function (enable) { | ||||
|       let self = this | ||||
|       if (enable) { | ||||
|         if (self.progressInterval) { | ||||
|           clearInterval(self.progressInterval) | ||||
|         } | ||||
|         self.progressInterval = setInterval(() => { | ||||
|           self.updateProgress() | ||||
|         }, 1000) | ||||
|       } else { | ||||
|         this.$store.dispatch('player/trackEnded', this.realTrack) | ||||
|         clearInterval(self.progressInterval) | ||||
|       } | ||||
|     }, | ||||
|     setCurrentTime (t) { | ||||
|       if (t < 0 | t > this.duration) { | ||||
|         return | ||||
|       } | ||||
|       if (t === this.$refs.audio.currentTime) { | ||||
|       if (t === this.sound.seek()) { | ||||
|         return | ||||
|       } | ||||
|       if (t === 0) { | ||||
|         this.updateProgressThrottled.cancel() | ||||
|       } | ||||
|       this.$refs.audio.currentTime = t | ||||
|       this.sound.seek(t) | ||||
|     }, | ||||
|     ended: function () { | ||||
|       let onlyTrack = this.$store.state.queue.tracks.length === 1 | ||||
|       if (this.looping === 1 || (onlyTrack && this.looping === 2)) { | ||||
|         this.sound.seek(0) | ||||
|         this.sound.play() | ||||
|       } else { | ||||
|         this.$store.dispatch('player/trackEnded', this.track) | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     track: _.debounce(function (newValue) { | ||||
|       this.realTrack = newValue | ||||
|       this.setCurrentTime(0) | ||||
|       this.$refs.audio.load() | ||||
|     }, 1000, {leading: true, trailing: true}), | ||||
|     playing: function (newValue) { | ||||
|       if (newValue === true) { | ||||
|         this.$refs.audio.play() | ||||
|         this.sound.play() | ||||
|       } else { | ||||
|         this.$refs.audio.pause() | ||||
|       } | ||||
|     }, | ||||
|     '$store.state.queue.currentIndex' () { | ||||
|       if (this.$store.state.player.playing) { | ||||
|         this.$refs.audio.play() | ||||
|         this.sound.pause() | ||||
|       } | ||||
|       this.observeProgress(newValue) | ||||
|     }, | ||||
|     volume: function (newValue) { | ||||
|       this.$refs.audio.volume = newValue | ||||
|       this.sound.volume(newValue) | ||||
|     }, | ||||
|     currentTime (newValue) { | ||||
|       if (!this.isUpdatingTime) { | ||||
|  |  | |||
|  | @ -3493,6 +3493,10 @@ hosted-git-info@^2.1.4: | |||
|   version "2.6.0" | ||||
|   resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" | ||||
| 
 | ||||
| howler@^2.0.14: | ||||
|   version "2.0.14" | ||||
|   resolved "https://registry.yarnpkg.com/howler/-/howler-2.0.14.tgz#28e37800fea002fea147a3ca033660c4f1288a99" | ||||
| 
 | ||||
| html-comment-regex@^1.1.0: | ||||
|   version "1.1.1" | ||||
|   resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" | ||||
|  |  | |||
		Ładowanie…
	
		Reference in New Issue
	
	 Eliot Berriot
						Eliot Berriot