LOADING...

Preview

Pen ID
Unlock Campus Themeforest adv

 

Code
CSS
@import url("https://fonts.googleapis.com/css?family=Roboto:400,500");
* {
  -webkit-box-sizing: border-box;
          box-sizing: border-box;
}
*::before, *::after {
  -webkit-box-sizing: inherit;
          box-sizing: inherit;
}

html,
body {
  min-height: 100vh;
}

body {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  font-family: 'Roboto', sans-serif;
  background-color: #191818;
}

:root {
  --color_mine_shaft: #252525;
  --color_tundora: #4D4D4D;
  --color_silver_chalice: #B0AFAF;
  --color_white: #fff;
  --color_mercury: #e8e8e8;
}

/**
* Loader animation map
*/
.melody__root,
#melody__root {
  position: relative;
  height: 410px;
  width: 350px;
  border-radius: 5px;
  background-color: var(--color_mine_shaft);
  color: var(--color_white);
  font-weight: 500;
  -webkit-box-shadow: 1px 1px 11px 0 rgba(0, 0, 0, 0.3);
          box-shadow: 1px 1px 11px 0 rgba(0, 0, 0, 0.3);
  overflow: hidden;
}

.melody__component {
  height: 100%;
}
.melody__component--fetching {
  display: none;
}
.melody__preview {
  position: relative;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-orient: vertical;
  -webkit-box-direction: normal;
      -ms-flex-direction: column;
          flex-direction: column;
  -webkit-box-pack: justify;
      -ms-flex-pack: justify;
          justify-content: space-between;
  min-height: 300px;
  padding: 30px;
  -webkit-box-shadow: 0px 1px 11px 1px rgba(0, 0, 0, 0.2);
          box-shadow: 0px 1px 11px 1px rgba(0, 0, 0, 0.2);
}
.melody__artwork {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-size: cover;
  background-repeat: no-repeat;
  -webkit-filter: brightness(50%) grayscale(50%);
          filter: brightness(50%) grayscale(50%);
}
.melody__previewHeader {
  position: relative;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: justify;
      -ms-flex-pack: justify;
          justify-content: space-between;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
}
.melody__previewFooter {
  position: relative;
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: justify;
      -ms-flex-pack: justify;
          justify-content: space-between;
}
.melody__time {
  text-shadow: 0 0 60px #000;
}
.melody__volume {
  cursor: ns-resize !important;
}
.melody__volume:hover circle, .melody__volume:active circle {
  opacity: 1;
}
.melody__volume .melody__icon {
  max-width: 21px;
  width: 100%;
  height: 100%;
  fill: var(--color_white);
  overflow: visible;
}
.melody__volume circle {
  opacity: 0;
  fill: transparent;
  stroke-width: 1px;
  stroke: var(--color_white);
  stroke-dasharray: 75;
  stroke-linecap: round;
  -webkit-transform-origin: center center;
          transform-origin: center center;
  -webkit-transform: scale(1.5) rotate(90deg);
          transform: scale(1.5) rotate(90deg);
  -webkit-transition: opacity .15s ease;
  transition: opacity .15s ease;
}
.melody__marquee {
  position: relative;
  text-align: center;
}
.melody__controlBar {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  -webkit-box-pack: justify;
      -ms-flex-pack: justify;
          justify-content: space-between;
  padding: 30px;
}
.melody__overlay {
  display: none;
}
.melody__btn {
  border: none;
  background: transparent;
  cursor: pointer;
  padding: 0;
}
.melody__btn:focus {
  outline: 0;
}
.melody__btn:active {
  -webkit-transform: scale(0.95);
          transform: scale(0.95);
}
.melody__favCtrl .melody__icon {
  max-width: 20px;
  width: 100%;
  height: 100%;
  fill: transparent;
  stroke: var(--color_white);
  stroke-width: 2px;
  overflow: visible;
}
.melody__favCtrl .melody__icon path:last-child {
  opacity: 0;
}
.melody__favCtrl--active .melody__icon {
  fill: red;
}
.melody__favCtrl--active .melody__icon path:last-child {
  -webkit-transform-origin: center center;
          transform-origin: center center;
  fill: red;
  stroke: transparent;
  stroke-width: 0;
  -webkit-animation: ghost .5s ease;
          animation: ghost .5s ease;
}
.melody__title, .melody__body {
  letter-spacing: 0.02em;
}
.melody__title {
  font-size: 24px;
  margin-bottom: 0.7em;
  text-shadow: 0 0 20px black;
}
.melody__body {
  font-size: 16px;
  color: var(--color_mercury);
}
.melody__glider {
  position: relative;
  margin-bottom: -2em;
}
.melody__glider__playback {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  height: 16px;
  padding: 0 14px 0 0;
  overflow: hidden;
  cursor: pointer;
}
.melody__glider__trackbar {
  position: relative;
  background-color: var(--color_white);
  margin-left: 0;
  background-color: transparent;
}
.melody__glider__trackbar::before, .melody__glider__trackbar::after {
  content: '';
  position: absolute;
  top: 50%;
  -webkit-transform: translateY(-50%);
          transform: translateY(-50%);
  height: 5px;
  width: 9999px;
}
.melody__glider__trackbar::before {
  right: 0;
  background-color: var(--color_white);
}
.melody__glider__trackbar::after {
  left: 100%;
  background-color: var(--color_silver_chalice);
}
.melody__glider__handle {
  position: relative;
  z-index: 1;
  padding: 7px;
  border-radius: 50%;
  -webkit-box-shadow: 2px 0px 2px 1px rgba(0, 0, 0, 0.2);
          box-shadow: 2px 0px 2px 1px rgba(0, 0, 0, 0.2);
  background-color: var(--color_white);
  cursor: ew-resize !important;
}
.melody__playbackCtrl .melody__icon {
  max-width: 30px;
  width: 100%;
  height: 100%;
  fill: var(--color_tundora);
}
.melody__playbackCtrl:hover .melody__icon {
  fill: var(--color_silver_chalice);
}
.melody__playbackCtrl--active .melody__icon {
  fill: var(--color_white);
}
.melody__playbackCtrl--active:hover .melody__icon {
  fill: var(--color_white);
}
.melody__playbackCtrl--cc {
  border-radius: 50%;
  border: 2px solid var(--color_tundora);
  padding: 7px 10px;
}
.melody__playbackCtrl--cc .melody__icon {
  fill: var(--color_silver_chalice);
}
.melody__playbackCtrl--cc:hover .melody__icon {
  fill: var(--color_white);
}
.melody__buffer circle {
  fill: var(--color_white);
  -webkit-transform-origin: center center;
          transform-origin: center center;
  -webkit-animation-name: buffer;
          animation-name: buffer;
  -webkit-animation-timing-function: ease;
          animation-timing-function: ease;
  -webkit-animation-iteration-count: infinite;
          animation-iteration-count: infinite;
  -webkit-animation-fill-mode: forwards;
          animation-fill-mode: forwards;
  animation-direction: reverse;
  -webkit-animation-duration: 1.25s;
          animation-duration: 1.25s;
  opacity: 0;
  -webkit-transform: scale(0.8);
          transform: scale(0.8);
}
.melody__buffer circle:first-of-type {
  -webkit-animation-delay: 0.25s;
          animation-delay: 0.25s;
}
.melody__buffer circle:nth-of-type(2) {
  -webkit-animation-delay: 0.5s;
          animation-delay: 0.5s;
}
.melody__buffer circle:last-of-type {
  -webkit-animation-delay: 0.75s;
          animation-delay: 0.75s;
}
.melody__loader {
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -webkit-box-pack: center;
      -ms-flex-pack: center;
          justify-content: center;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
  width: 100%;
  height: 100%;
}
.melody__loaderIcon {
  width: 3em;
  height: 3em;
  fill: var(--color_tundora);
}
.melody__loaderIcon rect {
  -webkit-transform-origin: bottom center;
          transform-origin: bottom center;
}
.melody__loaderIcon__c1 rect:first-child {
  -webkit-animation: c1-cap 1.25s ease 0s infinite alternate;
          animation: c1-cap 1.25s ease 0s infinite alternate;
}
.melody__loaderIcon__c1 rect:last-child {
  -webkit-animation: c1 1.25s ease 0s infinite alternate;
          animation: c1 1.25s ease 0s infinite alternate;
}
.melody__loaderIcon__c2 rect:first-child {
  -webkit-animation: c2-cap 1s ease 0s infinite alternate;
          animation: c2-cap 1s ease 0s infinite alternate;
}
.melody__loaderIcon__c2 rect:last-child {
  -webkit-animation: c2 1s ease 0s infinite alternate;
          animation: c2 1s ease 0s infinite alternate;
}
.melody__loaderIcon__c3 rect:first-child {
  -webkit-animation: c3-cap 0.5s ease 0s infinite alternate;
          animation: c3-cap 0.5s ease 0s infinite alternate;
}
.melody__loaderIcon__c3 rect:last-child {
  -webkit-animation: c3 0.5s ease 0s infinite alternate;
          animation: c3 0.5s ease 0s infinite alternate;
}
.melody__loaderIcon__c4 rect:first-child {
  -webkit-animation: c4-cap 0.6s ease 0s infinite alternate;
          animation: c4-cap 0.6s ease 0s infinite alternate;
}
.melody__loaderIcon__c4 rect:last-child {
  -webkit-animation: c4 0.6s ease 0s infinite alternate;
          animation: c4 0.6s ease 0s infinite alternate;
}
.melody__loaderIcon__c5 rect:first-child {
  -webkit-animation: c5-cap 0.6s ease 0s infinite alternate;
          animation: c5-cap 0.6s ease 0s infinite alternate;
}
.melody__loaderIcon__c5 rect:last-child {
  -webkit-animation: c5 0.6s ease 0s infinite alternate;
          animation: c5 0.6s ease 0s infinite alternate;
}
.melody__loaderIcon__c6 rect:first-child {
  -webkit-animation: c6-cap 1.1s ease 0s infinite alternate;
          animation: c6-cap 1.1s ease 0s infinite alternate;
}
.melody__loaderIcon__c6 rect:last-child {
  -webkit-animation: c6 1.1s ease 0s infinite alternate;
          animation: c6 1.1s ease 0s infinite alternate;
}

/**
* Keyframes
*/
@-webkit-keyframes c1 {
  0% {
    -webkit-transform: scaleY(0.3);
            transform: scaleY(0.3);
  }
  60% {
    -webkit-transform: scaleY(0.7);
            transform: scaleY(0.7);
  }
  80% {
    -webkit-transform: scaleY(0.1);
            transform: scaleY(0.1);
  }
  100% {
    -webkit-transform: scaleY(1);
            transform: scaleY(1);
  }
}
@keyframes c1 {
  0% {
    -webkit-transform: scaleY(0.3);
            transform: scaleY(0.3);
  }
  60% {
    -webkit-transform: scaleY(0.7);
            transform: scaleY(0.7);
  }
  80% {
    -webkit-transform: scaleY(0.1);
            transform: scaleY(0.1);
  }
  100% {
    -webkit-transform: scaleY(1);
            transform: scaleY(1);
  }
}
@-webkit-keyframes c1-cap {
  from {
    -webkit-transform: translateY(200%);
            transform: translateY(200%);
  }
}
@keyframes c1-cap {
  from {
    -webkit-transform: translateY(200%);
            transform: translateY(200%);
  }
}
@-webkit-keyframes c2 {
  0% {
    -webkit-transform: scaleY(0.1);
            transform: scaleY(0.1);
  }
  20% {
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
  }
  90% {
    -webkit-transform: scaleY(1);
            transform: scaleY(1);
  }
  100% {
    -webkit-transform: scaleY(0.6);
            transform: scaleY(0.6);
  }
}
@keyframes c2 {
  0% {
    -webkit-transform: scaleY(0.1);
            transform: scaleY(0.1);
  }
  20% {
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
  }
  90% {
    -webkit-transform: scaleY(1);
            transform: scaleY(1);
  }
  100% {
    -webkit-transform: scaleY(0.6);
            transform: scaleY(0.6);
  }
}
@-webkit-keyframes c2-cap {
  from {
    -webkit-transform: translateY(300%);
            transform: translateY(300%);
  }
}
@keyframes c2-cap {
  from {
    -webkit-transform: translateY(300%);
            transform: translateY(300%);
  }
}
@-webkit-keyframes c3 {
  18% {
    -webkit-transform: scaleY(0.8);
            transform: scaleY(0.8);
  }
  60% {
    -webkit-transform: scaleY(0.3);
            transform: scaleY(0.3);
  }
  90% {
    -webkit-transform: scaleY(1);
            transform: scaleY(1);
  }
}
@keyframes c3 {
  18% {
    -webkit-transform: scaleY(0.8);
            transform: scaleY(0.8);
  }
  60% {
    -webkit-transform: scaleY(0.3);
            transform: scaleY(0.3);
  }
  90% {
    -webkit-transform: scaleY(1);
            transform: scaleY(1);
  }
}
@-webkit-keyframes c3-cap {
  from {
    -webkit-transform: translateY(40%);
            transform: translateY(40%);
  }
}
@keyframes c3-cap {
  from {
    -webkit-transform: translateY(40%);
            transform: translateY(40%);
  }
}
@-webkit-keyframes c4 {
  0% {
    -webkit-transform: scaleY(0.1);
            transform: scaleY(0.1);
  }
  20% {
    -webkit-transform: scaleY(0.8);
            transform: scaleY(0.8);
  }
  90% {
    -webkit-transform: scaleY(1);
            transform: scaleY(1);
  }
  100% {
    -webkit-transform: scaleY(0.6);
            transform: scaleY(0.6);
  }
}
@keyframes c4 {
  0% {
    -webkit-transform: scaleY(0.1);
            transform: scaleY(0.1);
  }
  20% {
    -webkit-transform: scaleY(0.8);
            transform: scaleY(0.8);
  }
  90% {
    -webkit-transform: scaleY(1);
            transform: scaleY(1);
  }
  100% {
    -webkit-transform: scaleY(0.6);
            transform: scaleY(0.6);
  }
}
@-webkit-keyframes c4-cap {
  from {
    -webkit-transform: translateY(300%);
            transform: translateY(300%);
  }
}
@keyframes c4-cap {
  from {
    -webkit-transform: translateY(300%);
            transform: translateY(300%);
  }
}
@-webkit-keyframes c5 {
  19% {
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
  }
  79% {
    -webkit-transform: scaleY(0.8);
            transform: scaleY(0.8);
  }
  87% {
    -webkit-transform: scaleY(0.9);
            transform: scaleY(0.9);
  }
}
@keyframes c5 {
  19% {
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
  }
  79% {
    -webkit-transform: scaleY(0.8);
            transform: scaleY(0.8);
  }
  87% {
    -webkit-transform: scaleY(0.9);
            transform: scaleY(0.9);
  }
}
@-webkit-keyframes c5-cap {
  from {
    -webkit-transform: translateY(100%);
            transform: translateY(100%);
  }
}
@keyframes c5-cap {
  from {
    -webkit-transform: translateY(100%);
            transform: translateY(100%);
  }
}
@-webkit-keyframes c6 {
  46% {
    -webkit-transform: scaleY(0.3);
            transform: scaleY(0.3);
  }
  69% {
    -webkit-transform: scaleY(0.8);
            transform: scaleY(0.8);
  }
  75% {
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
  }
  100% {
    -webkit-transform: scaleY(0.2);
            transform: scaleY(0.2);
  }
}
@keyframes c6 {
  46% {
    -webkit-transform: scaleY(0.3);
            transform: scaleY(0.3);
  }
  69% {
    -webkit-transform: scaleY(0.8);
            transform: scaleY(0.8);
  }
  75% {
    -webkit-transform: scaleY(0.5);
            transform: scaleY(0.5);
  }
  100% {
    -webkit-transform: scaleY(0.2);
            transform: scaleY(0.2);
  }
}
@-webkit-keyframes c6-cap {
  from {
    -webkit-transform: translateY(40%);
            transform: translateY(40%);
  }
}
@keyframes c6-cap {
  from {
    -webkit-transform: translateY(40%);
            transform: translateY(40%);
  }
}
@-webkit-keyframes buffer {
  0% {
    -webkit-transform: scale(0);
            transform: scale(0);
    opacity: 0;
  }
  50% {
    opacity: 0;
  }
  100% {
    -webkit-transform: scale(0.8);
            transform: scale(0.8);
    opacity: 0.5;
  }
}
@keyframes buffer {
  0% {
    -webkit-transform: scale(0);
            transform: scale(0);
    opacity: 0;
  }
  50% {
    opacity: 0;
  }
  100% {
    -webkit-transform: scale(0.8);
            transform: scale(0.8);
    opacity: 0.5;
  }
}
@-webkit-keyframes ghost {
  from {
    -webkit-transform: scale(0);
            transform: scale(0);
    opacity: 0.5;
  }
  to {
    -webkit-transform: scale(3);
            transform: scale(3);
    opacity: 0;
  }
}
@keyframes ghost {
  from {
    -webkit-transform: scale(0);
            transform: scale(0);
    opacity: 0.5;
  }
  to {
    -webkit-transform: scale(3);
            transform: scale(3);
    opacity: 0;
  }
}
JS
/** @jsx h */

/**
* UTILS
*/
const utils = {
    getLayerX(node, clientX) {
        let el = node;
        let x = 0;
        
        while (el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
            x += el.offsetLeft - el.scrollLeft;
            el = el.offsetParent;
        }
        
        return clientX - x;
    },
    
    getRandomNumInRange(min, max) {
        return Math.floor(Math.random() * (max - min) + min);
    },
    
    formatTime(time) {
        const hours = Math.floor(time / 3600);
        const minutes = Math.floor((time % 3600) / 60);
        const seconds = time % 60;
        let formatted = '';
        
        if (hours > 0) {
            formatted += `${hours}:${minutes < 10 ? '0' : ''}`;
        }
        
        formatted += `${minutes}:${seconds < 10 ? '0' : ''}`;
        formatted += seconds;
        return formatted;
    },
}

const { h, render, Component } = window.preact;

const states = {
    initialState: 'fetching',
    fetching: {
        FAILED: 'fault',
        SUCCESS: 'stopped',
    },
    buffering: {
        READY: 'stopped',
        PROCEED: 'playing',
        FAILED: 'fault',
    },
    playing: {
        STOP: 'stopped',
        LOADING: 'buffering',
        FAILED: 'fault',
    },
    stopped: {
        PLAY: 'playing',
        LOADING: 'buffering',
        FAILED: 'fault',
    },
    fault: {
        RELOAD: 'fetching',
    },
};

/**
* Melody
*/
class Melody extends Component {
    state = {
        currentState: '',
        lastState: '',
        tracks: [],
        currentTrack: null,
        shuffle: false,
        repeat: false,
        volume: 0.5,
        currentTime: 0,
        gliderIsDragging: false,
        sourceUrl: '',
    };

    interface;

    constructor(props) {
        super(props);
        this.interface = new window.Audio();
        this.interface.volume = 0.5; // @TODO REMOVE? or start at 0.5?
        const { initialState } = props.states;
        this.setState({
            currentState: initialState,
            sourceUrl: props.sourceUrl,
        }, this.fetchTracks);
    }

    componentDidMount() {
        this.bindEvents();
    }

    componentWillUnmount() {
        this.removeEvents();
    }

    bindEvents() {
        this.interface.addEventListener('waiting', this.onInterfaceBuffering);
        this.interface.addEventListener('canplay', this.onCanPlay);
        this.interface.addEventListener('timeupdate', this.onTimeUpdate);
        this.interface.addEventListener('ended', this.nextTrack);
    }
    
    removeEvents() {
        this.interface.removeEventListener('waiting', this.onInterfaceBuffering);
        this.interface.removeEventListener('canplay', this.onCanPlay);
        this.interface.removeEventListener('timeupdate', this.onTimeUpdate);
        this.interface.removeEventListener('ended', this.nextTrack);
    }

    transition(action) {
        const { currentState } = this.state;        
        const { states } = this.props;
        const nextState = states[currentState][action];

        if (!nextState) {
            if (currentState === 'fault') {
                return;
            }
            
            this.transition('FAILED');
            return;
        }
        
        switch (nextState) {
            case 'fetching':
            case 'stopped':
            case 'fault': {
                this.interface.pause();
                break;
            }
            case 'playing': {
                this.interface.play()
                    .catch((e) => {
                        if (e.name && e.name !== 'AbortError') {
                            this.transition('FAILED');
                        }
                    });
                break;
            }
            case 'buffering': {
                if (currentState !== 'playing') {
                    this.interface.pause();
                }
                break;
            }
            default: {
                // stop if the state is unknown
                this.interface.pause();
                break;
            }
        };
        
        this.setState({
            currentState: nextState,
            lastState: currentState,
        });
    }

    onInterfaceBuffering = () => {
        this.transition('LOADING');
    }
    
    onCanPlay = () => {
        const { currentState, lastState } = this.state;
        
        if (currentState !== 'buffering') {
            return;
        }
        
        if (lastState === 'playing') {
            this.transition('PROCEED');
            return;
        }

        this.transition('READY');
    }

    fetchTracks() {
        const { sourceUrl } = this.state;
        window.fetch(sourceUrl)
            .then(res => res.json())
            .then(tracks => {
                this.setState({
                    tracks,
                    currentTrack: 0,
                }, this.setSource);
                this.transition('SUCCESS');
            })
            .catch(() => this.transition('REJECTED'));
    }

    setSource() {
        const { currentState, currentTrack, tracks } = this.state;
        const nextSource = tracks[currentTrack].source_url;
        
        if (this.interface.src === nextSource) {
            return;
        }
        
        if (currentState === 'playing') {
            this.transition('STOP');
            this.interface.src = nextSource;
            this.interface.load();
            this.transition('PLAY');
        } else {
            this.interface.src = nextSource;
            this.interface.load();
        }
        // this.transition('STOP');// @TODO: CHANGE
    }

    nextTrack = () => {
        const { tracks, currentTrack, shuffle } = this.state;
        
        if (tracks.length <= 1) {
            return;
        }
        
        if (shuffle) {
            this.shuffleTracks();
            return;
        }
        
        let index = currentTrack + 1;
        if (index > (tracks.length -1)) {
            index = 0;
        }
        
        this.setState({
            currentTrack: index,
        }, this.setSource);
    };

    prevTrack = () => {
        const { tracks, currentTrack, shuffle } = this.state;
        
        if (tracks.length <= 1) {
            return;
        }
        
        let index = currentTrack - 1;
        
        if (index < 0) {
            index = tracks.length - 1;
        }
        
        this.setState({
            currentTrack: index,
        }, this.setSource);
    };

    shuffleTracks() {
        const { tracks, currentTrack } = this.state;
        let index = utils.getRandomNumInRange(0, tracks.length);
        
        while (index === currentTrack) {
            index = utils.getRandomNumInRange(0, tracks.length);
        }
        
        this.setState({
            currentTrack: index,
        });
    }

    toggleShuffle = () => {
        const { shuffle } = this.state;
        this.setState({
            shuffle: !shuffle,
        });
        return;
    }

    toggleRepeat = () => {
        const { repeat } = this.state;
        this.interface.loop = !repeat;
        this.setState({
            repeat: !repeat,
        });
    }

    toggleFav = () => {
        const { currentTrack, tracks } = this.state;
        const track = tracks[currentTrack];
        track.favorited = !track.favorited;
        this.setState({ tracks });
    };

    toggleGliderDragging = () => {
        const { gliderIsDragging } = this.state;
        this.setState({
            gliderIsDragging: !gliderIsDragging,
        });
    }

    updateVolume = (newVolume) => {
        if (isNaN(newVolume)) {
            return;
        }
        
        const { volume } = this.state;
        
        if (newVolume === volume) {
            return;
        }

        this.interface.volume = newVolume;
        this.setState({ volume: newVolume }); 
    };
    
    onTimeUpdate = () => {
        const nextTime = this.interface.currentTime.toFixed(0);
        const { currentTime, gliderIsDragging } = this.state;
        
        if (nextTime === currentTime || gliderIsDragging) {
            return;
        }

        this.setState({
            currentTime: nextTime,
        });
    }
    
    setCurrentTime = (nextTime, updateInterface = true) => {
        this.setState({
            currentTime: nextTime,
        });
        
        if (updateInterface) {
            this.interface.currentTime = nextTime;
        }
    }

    render({}, state) {
        const { currentState } = state;
        return (
            
{this[currentState]()}
); } fetching() { // show loader return null; } buffering() { const { currentState, shuffle, repeat, tracks, currentTrack, volume, currentTime, } = this.state; return (
this.transition('PLAY')} />
); } playing() { const { currentState, shuffle, repeat, tracks, currentTrack, volume, currentTime, } = this.state; return (
this.transition('STOP')} />
); } stopped() { const { currentState, shuffle, repeat, tracks, currentTrack, volume, currentTime, } = this.state; return (
this.transition('PLAY')} />
); } fault() { return (
lol fault
); } } const DragHelper = (WrappedComponent) => { return class extends Component { state = { isDragging: false, clientX: 0, clientY: 0, } ref; registry = new Map(); componentDidMount() { this.ref.draggable = true; document.addEventListener('dragover', this.updateClientOffset); this.ref.addEventListener('dragstart', this.fire); this.ref.addEventListener('dragstart', this.initDragEvent); this.ref.addEventListener('drag', this.fire); this.ref.addEventListener('dragend', this.endDragEvent); this.ref.addEventListener('dragend', this.fire); } componentWillUnmount() { document.removeEventListener('dragover', this.updateClientOffset); this.ref.removeEventListener('dragstart', this.fire); this.ref.removeEventListener('dragstart', this.initDragEvent); this.ref.removeEventListener('drag', this.fire); this.ref.removeEventListener('dragend', this.endDragEvent); this.ref.removeEventListener('dragend', this.fire); } setDragRef = (ref) => { this.ref = ref; } updateClientOffset = ({ clientX, clientY }) => { if (!this.state.isDragging) { return; } this.setState({ clientX, clientY, }); } initDragEvent = ({ dataTransfer }) => { // don't show drag preview const dragPreview = document.createElement('div'); dragPreview.style.visibility = 'hidden'; dataTransfer.setDragImage(dragPreview, 0, 0); // avoid weird drag cursors dataTransfer.effectAllowed = 'none'; dataTransfer.mozCursor = 'default'; // "initialization" for firefox dataTransfer.setData('key', this.ref.tagName); this.setState({ isDragging: true, }); } endDragEvent = () => { this.setState({ isDragging: false, }); } on(type, fn) { if (this.registry.has(type)) { const handlers = this.registry.get(type); if (handlers.has(fn)) { return; } handlers.add(fn); return; } this.registry.set(type, new Set([fn])); } fire = (e) => { const { type } = e; if (this.registry.has(type)) { const eCopy = Object.assign({}, e); if (['drag', 'dragend'].includes(type)) { eCopy.clientX = this.state.clientX; eCopy.clientY = this.state.clientY; } this.registry.get(type).forEach(fn => fn.call(WrappedComponent, eCopy)); } } render() { return ( this.on('dragstart', fn)} onDrag={fn => this.on('drag', fn)} onDragEnd={fn => this.on('dragend', fn)} {...this.props} /> ); } } } /** * ControlBar */ const ControlBar = (props) => { const { currentState, toggleRepeat, toggleShuffle, shuffle, repeat, next, prev, transition, } = props; const classNames = { default: 'melody__playbackCtrl', active: 'melody__playbackCtrl melody__playbackCtrl--active', }; return (
{currentState === 'buffering' ?
: }
); } /** * Preview */ const Preview = (props) => { const { toggleFav, track, volume, updateVolume, currentTime, setCurrentTime, currentState, toggleIsDragging, } = props; // const { // artist, // name, // artwork, // favorited, // duration // } = track; const { artist, title, length: duration, } = track.media_details; // console.log(track) const favorited = false; const getArtwork = () => { const { artwork } = track; const style = { backgroundImage: 'url(https://source.unsplash.com/random/350X300)', }; if (artwork) { style.backgroundImage = `url(${artwork})`; } return style; } return (

{title}

{artist}

); } /** * Preview Header */ const PreviewHeader = (props) => { const { favorited, toggleFav, volume, updateVolume, } = props; return (
); } /** * Favorite Button */ const FavoriteBtn = ({ toggle, active }) => { const classes = { default: 'melody__favCtrl', active: 'melody__favCtrl melody__favCtrl--active', }; return ( ); } /** * Volume Control */ class VolumeCtrl extends Component { state = { originalClientY: 0, lastClientY: 0, lastDirection: null, }; componentDidMount() { const { onDragStart, onDrag } = this.props; onDragStart(this.dragStart); onDrag(this.setVolume); } preventDragReset(e) { e.preventDefault(); } getPath() { const { level } = this.props; if (level < 0.1) { return ; } if (level > 0.1 && level < 0.8) { return ; } if (level > 0.8) { return ; } } getDashOffset() { const { level } = this.props; const ceiling = 75; const offset = ceiling - (level * ceiling); return { strokeDashoffset: `${offset}`, }; } onTouchStart = ({ touches }) => { const { clientY } = touches[0]; this.setState({ originalClientY: clientY, }); } dragStart = ({ clientY }) => { this.setState({ originalClientY: clientY, }); }; setVolume = (e) => { const { clientY } = e; const { lastClientY, lastDirection } = this.state let { originalClientY } = this.state; if (clientY === lastClientY) { return; } this.setState({ lastClientY: clientY }); const direction = lastClientY > clientY ? 'up' : 'down'; if (direction !== null && direction !== lastDirection) { originalClientY = clientY; this.setState({ lastDirection: direction, originalClientY: clientY, }); } const { level, updateVolume } = this.props; const maxRange = 3000; // sensitivity const delta = originalClientY - clientY; const percentChange = (delta / maxRange); let newLevel = percentChange + level; if (newLevel < 0) { newLevel = 0; this.setState({ originalClientY: clientY, }); } if (newLevel > 1) { newLevel = 1; this.setState({ originalClientY: clientY, }); } updateVolume(newLevel); }; render({ level }) { const { setDragRef } = this.props; return (
setDragRef(ref)} ontouchstart={this.onTouchStart} ontouchmove={({ touches }) => this.setVolume(touches[0])} > {this.getPath()}
); } } const DragVolumeCtrl = DragHelper(VolumeCtrl); /** * Preview Footer */ const PreviewFooter = ({ duration, currentTime }) => { const formatTime = (time) => { if (time > duration) { return utils.formatTime(duration); } if (time < 0) { return utils.formatTime(0); } return utils.formatTime(time); } return (
{formatTime(currentTime)} {formatTime(duration - currentTime)}
); } /** * Glider */ class Glider extends Component { state = { transitionHandle: false, offset: 0, clientX: 0, } ref; componentDidMount() { const { onDragStart, onDrag, onDragEnd } = this.props; onDragStart(this.onDragStart); onDrag(this.onDrag); onDragEnd(this.onDragEnd); } componentWillReceiveProps(nextProps) { if (this.props.currentTime !== nextProps.currentTime) { const { duration, currentTime } = nextProps; const nextOffset = (100 / (duration / currentTime)).toFixed(0); this.updateOffset(nextOffset); } } setRef = (el) => { if (el instanceof HTMLElement) { const { setDragRef } = this.props; this.ref = el; setDragRef(el); } } updateOffset(nextOffset) { this.setState({ offset: nextOffset, }); } handleClick = ({ clientX }, ignoreHandle = true) => { if (!this.ref) { return; } const width = this.ref.offsetWidth; const x = utils.getLayerX(this.ref, clientX); if (ignoreHandle) { const currentOffset = this.state.offset; const handleArea = 14; const handleSpacing = 2; const rightPadding = handleArea / 2; const handleCenter = (currentOffset * width) / 100; const lowerBoundary = handleCenter - (handleArea + handleSpacing) + rightPadding; const upperBoundary = handleCenter + handleArea + handleSpacing; const withinHandle = x >= lowerBoundary && x <= upperBoundary; if (withinHandle) { return; } } const { duration, setCurrentTime } = this.props; const xPercent = 100 / (width / x); const updatedTime = ((duration * xPercent) / 100).toFixed(0); setCurrentTime(updatedTime); } onTouchStart = ({ touches }) => { const { clientX } = touches[0]; this.setState({ originalClientY: clientX, }); } onDragStart = ({ clientX }) => { this.props.toggleIsDragging(); this.setState({ clientX, }); }; onDragEnd = (e) => { this.props.toggleIsDragging(); this.handleClick(e, false); } onDrag = ({ clientX }) => { if (!this.ref) { // show error? call func to display error start? transitionComponent? return; } const originalClientX = this.state.clientX; if (clientX === originalClientX) { return; } const { offset } = this.state; const { duration, setCurrentTime } = this.props; const width = this.ref.offsetWidth; const x = utils.getLayerX(this.ref, clientX); let newOffset = Number(((x / width) * 100).toFixed(2)); if (newOffset > 100) { newOffset = 100; } if (newOffset < 0) { newOffset = 0; } this.updateOffset(newOffset); const updatedTime = ((duration * newOffset) / 100).toFixed(0); setCurrentTime(updatedTime, false); } render({ currentState }, { offset }) { const { setDragRef } = this.props; const classes = { default: 'melody__glider__trackbar', transition: 'melody__glider__trackbar melody__glider__trackbar--transition', }; const handleClass = currentState === 'playing' && offset > 0 ? classes.transition : classes.default; return (
this.onDrag(touches[0])} ontouchend={({ changedTouches }) => this.onDragEnd(changedTouches[0])} >
); } } const DragGlider = DragHelper(Glider); /** * BaseButton */ const BaseButton = (props) => ( ); /** * Icon */ const Icon = ({ name, className }) => { const getMarkup = (name) => { switch(name) { case 'shuffle': { return ; } case 'skipBack': { return ; } case 'play': { return ; } case 'pause': { return ; } case 'skipForward': { return ; } case 'repeat': { return ; } case 'buffer': { return ( ); } default: { return ''; } } }; return ( {getMarkup(name)} ); } /** * Initialization */ const sourceUrl = 'https://jarens.me/wp-json/wp/v2/media?media_type=audio&context=embed'; const melodyRoot = document.getElementById('melody__root'); render( , melodyRoot, melodyRoot.firstChild );
Term
Wed, 12/27/2017 - 07:06

Related Codes

Pen ID
Pen ID
Pen ID
Square Adv