<div id="site" data-namespace="">
    <ul class="o-podcast-track-list u-list-clean js-track-list"></ul>

    <div class="o-podcast-player o-podcast-player--hidden js-podcast" data-rss="https://internetpodden.libsyn.com/rss">
        <div class="wrapper">
            <div class="row justify-content-between align-items-center">
                <img src="https://ssl-static.libsyn.com/p/assets/a/9/6/5/a9653e010511f97e/internetpodden-logo.png" class="o-podcast-player__player__image js-image" alt="false">
                <div class="o-podcast-player__player">
                    <div class="o-podcast-player__player__show-info">
                        <h4 class="o-podcast-player__title color-ruby js-title"></h4>
                        <div class="display-flex align-items-center">
                            <time class="o-podcast-player__playtime js-duration"></time>
                            <span class="o-podcast-player__spacer">-</span>
                            <div class="o-podcast-player__description js-description"></div>
                        </div>
                    </div>
                    <div class="o-podcast-player__controls">
                        <button type="button" class="o-podcast-player__button js-step-backward" title="Back 15 seconds">
                            <span class="u-visuallyhidden">Back 15 seconds</span>
                            <svg class="icon o-podcast-player__rewind-icon">
                                <use xlink:href="#icon-backward-15"></use>
                            </svg>
                        </button>
                        <button type="button" class="o-podcast-player__button o-podcast-player__button--play js-play-button" title="Play/Pause">
                            <span class="u-visuallyhidden">Play/Pause</span>
                            <svg class="icon o-podcast-player__play-icon js-play-icon">
                                <use xlink:href="#icon-play"></use>
                            </svg>
                            <svg class="icon o-podcast-player__play-icon js-pause-icon is-hidden">
                                <use xlink:href="#icon-pause"></use>
                            </svg>
                        </button>
                        <button type="button" class="o-podcast-player__button js-step-forward" title="Forward 60 seconds">
                            <span class="u-visuallyhidden">Forward 60 seconds</span>
                            <svg class="icon o-podcast-player__rewind-icon">
                                <use xlink:href="#icon-forward-60"></use>
                            </svg>
                        </button>
                        <time class="o-podcast-player__timeleft u-visibility-hidden js-timeleft"></time>
                        <audio controls id="podcastPlayer" class="u-visuallyhidden"></audio>
                    </div>
                </div>
            </div>
        </div>
        <button class="a-button a-button--standalone-icon a-button--icon o-podcast-player__close js-close-player">
            <span class="a-button__text">Sträng</span>
            <svg class=" icon a-button__icon">
                <use xlink:href="#icon-close"></use>
            </svg>
        </button>
        <div class="o-podcast-player__progressbar">
            <div class="o-podcast-player__progressbar__progress background-ruby js-progress"></div>
        </div>
    </div>
</div>
<div id="site" data-namespace="">
	{{#if playlist}}
	<ul class="o-podcast-track-list u-list-clean js-track-list"></ul>
	{{/if}}

	<div class="o-podcast-player{{#if hidden}} o-podcast-player--hidden{{/if}} js-podcast" data-rss="https://internetpodden.libsyn.com/rss">
		<div class="wrapper">
			<div class="row justify-content-between align-items-center">
				<img src="{{episodeImage}}" class="o-podcast-player__player__image js-image" alt="{{title}}">
				<div class="o-podcast-player__player">
					<div class="o-podcast-player__player__show-info">
						<h4 class="o-podcast-player__title color-ruby js-title">{{#if title}}{{title}}{{/if}}</h4>
						<div class="display-flex align-items-center">
							<time class="o-podcast-player__playtime js-duration">{{#if duration}}{{duration}}{{/if}}</time>
							<span class="o-podcast-player__spacer">-</span>
							<div class="o-podcast-player__description js-description">{{#if description}}{{description}}{{/if}}</div>
						</div>
					</div>
					<div class="o-podcast-player__controls">
						<button type="button" class="o-podcast-player__button js-step-backward" title="Back 15 seconds">
							<span class="u-visuallyhidden" >Back 15 seconds</span>
							{{>@ icon id="backward-15" additional_classes="o-podcast-player__rewind-icon"}}
						</button>
						<button type="button" class="o-podcast-player__button o-podcast-player__button--play js-play-button" title="Play/Pause">
							<span class="u-visuallyhidden">Play/Pause</span>
							{{>@ icon id="play" additional_classes="o-podcast-player__play-icon js-play-icon"}}
							{{>@ icon id="pause" additional_classes="o-podcast-player__play-icon js-pause-icon is-hidden"}}
						</button>
						<button type="button" class="o-podcast-player__button js-step-forward" title="Forward 60 seconds">
							<span class="u-visuallyhidden" >Forward 60 seconds</span>
							{{>@ icon id="forward-60" additional_classes="o-podcast-player__rewind-icon"}}
						</button>
						<time class="o-podcast-player__timeleft {{#unless duration}}u-visibility-hidden{{/unless}} js-timeleft">{{#if duration}}{{duration}}{{/if}}</time>
						<audio controls id="podcastPlayer" class="u-visuallyhidden" {{#if src}}src="{{src}}"{{/if}}></audio>
					</div>
				</div>
			</div>
		</div>
		<button class="a-button a-button--standalone-icon a-button--icon o-podcast-player__close js-close-player">
			<span class="a-button__text">Sträng</span>
			<svg class=" icon a-button__icon">
				<use xlink:href="#icon-close"></use>
			</svg>
		</button>
		<div class="o-podcast-player__progressbar">
			<div class="o-podcast-player__progressbar__progress background-ruby js-progress"></div>
		</div>
	</div>
</div>
{
  "playlist": true,
  "hidden": true,
  "src": false,
  "image": false,
  "title": false,
  "description": false,
  "duration": false,
  "has_modifier": false,
  "is_single_episode": false,
  "date": "15 december, 2020",
  "episodeImage": "https://ssl-static.libsyn.com/p/assets/a/9/6/5/a9653e010511f97e/internetpodden-logo.png"
}
  • Content:
    @charset "UTF-8";
    
    @include organism(podcast-episodes) {
    
    	// Episode
    	@include e(episode) {
    		justify-content: center;
    		margin-right: 0;
    		margin-left: 0;
    		padding: rhythm(2);
    		border-radius: $border-radius;
    		background-color: $color-snow;
    
    		@include bp-up(sm) {
    			padding-right: rhythm(2);
    			padding-left: rhythm(2);
    		}
    
    		@include bp-up(md) {
    			justify-content: flex-start;
    		}
    
    		@include bp-up(md) {
    			padding: rhythm(3);
    		}
    
    		@include m(list) {
    			margin-bottom: rhythm(2);
    
    			@include e(image) {
    				display: none;
    
    				@include bp-up(md) {
    					display: block;
    					width: rem(170px);
    					height: rem(170px);
    				}
    			}
    		}
    
    		@include m(hero) {
    			padding-right: 0;
    			padding-left: 0;
    			border-radius: 0;
    			background-color: transparent;
    
    			@include bp-up(md) {
    				flex-direction: row-reverse;
    				align-items: center;
    			}
    
    			@include e(image) {
    				width: rem(310px);
    				height: rem(310px);
    				margin-right: 0;
    				margin-left: rhythm(4);
    			}
    
    			@include e(info) {
    				> p {
    					overflow: visible;
    					text-overflow: inherit;
    					white-space: normal;
    				}
    			}
    
    			@include e(link) {
    				pointer-events: none;
    			}
    		}
    
    		@include e(image) {
    			margin-bottom: rhythm(2);
    			border-radius: $border-radius;
    
    			@include bp-down(sm) {
    				display: none;
    			}
    
    			@include bp-up(sm) {
    				display: block;
    				width: rem(255px);
    				height: rem(255px);
    				margin-right: rhythm(3);
    			}
    		}
    
    		@include e(meta) {
    			display: flex;
    			margin-bottom: rhythm(1);
    		}
    
    		@include e(info) {
    			h1 span {
    				pointer-events: none;
    			}
    
    			h1 svg {
    				width: $icon-size-large * 1.5;
    				height: $icon-size-large * 1.5;
    			}
    
    			h2 svg {
    				width: $icon-size-large * 1.2;
    				height: $icon-size-large * 1.2;
    			}
    
    			> p {
    				overflow: hidden;
    				text-overflow: ellipsis;
    				white-space: nowrap;
    
    				@include bp-up(sm-xs) {
    					overflow: visible;
    					text-overflow: inherit;
    					white-space: normal;
    				}
    			}
    		}
    
    		@include e(link) {
    			color: $color-cyberspace;
    			text-decoration: none;
    
    			&:hover,
    			&:focus {
    				text-decoration: underline;
    			}
    		}
    
    		@include e(button) {
    			display: flex;
    			align-items: center;
    			margin: 0 rhythm(1);
    			padding: 0;
    			border: 0;
    			background-color: transparent;
    
    			&:hover {
    				> svg {
    					fill: $color-ruby;
    				}
    			}
    
    			* {
    				pointer-events: none;
    			}
    		}
    
    		@include e(share) {
    			display: flex;
    			flex-wrap: wrap;
    			align-items: center;
    			margin-top: rhythm(1);
    
    			@include e(spacer) {
    				display: none;
    				margin-bottom: rhythm(2);
    
    				@include bp-up(lg) {
    					display: block;
    					margin-right: rhythm(2);
    					margin-bottom: 0;
    					margin-left: rhythm(2);
    					line-height: 1;
    				}
    			}
    
    			@include e(list) {
    				display: flex;
    				flex-basis: 100%;
    
    				> dt {
    					margin-right: rhythm(2);
    				}
    
    				> dd {
    					margin-left: 0;
    				}
    
    				@include bp-up(lg) {
    					flex-basis: auto;
    				}
    			}
    		}
    
    		@include e(listen-link) {
    			margin-right: rhythm(2);
    			color: $color-cyberspace;
    		}
    
    		@include e(download-link) {
    			display: flex;
    			flex-basis: 100%;
    			flex-grow: 1;
    			margin-bottom: rhythm(2);
    			color: $color-cyberspace;
    
    			> span {
    				font-family: $font-family-base;
    				font-size: $size-base;
    				text-decoration: underline;
    			}
    
    			> svg {
    				transform: translateY(3px);
    			}
    
    			@include bp-up(lg) {
    				margin-bottom: 0;
    			}
    
    			@include bp-up(md) {
    				flex-basis: auto;
    				flex-grow: 0;
    			}
    		}
    
    		@include e(download-icon) {
    			width: $icon-size;
    			height: $icon-size;
    			margin-right: rhythm(1);
    			padding: 3px;
    			border-radius: $border-radius;
    		}
    	}
    }
    
    // Track list (Prototype)
    @include organism(podcast-track-list) {
    	display: flex;
    	flex-direction: column;
    
    	> li {
    		display: flex;
    		padding: rhythm(1) rhythm(2);
    
    		+ li {
    			border-top: 1px solid $color-concrete;
    		}
    
    		> button {
    			display: flex;
    			align-items: center;
    		}
    	}
    }
    
  • URL: /components/raw/podcast/_podcast-episodes.scss
  • Filesystem Path: src/organisms/podcast/_podcast-episodes.scss
  • Size: 3.9 KB
  • Content:
    @charset "UTF-8";
    
    @use "sass:math";
    
    @include organism(podcast-player) {
    
    	// Player
    
    	$time-left-width: rem(65px);
    
    	position: fixed;
    	z-index: z_index(middlegroundImportant);
    	right: 0;
    	bottom: 0;
    	left: 0;
    	padding: rhythm(1) 0 0 0;
    	transform: translateY(0);
    	transition: transform 0.25s ease-out;
    	background-color: $color-snow;
    	box-shadow: 0 -#{rhythm(1)} rhythm(3) lighten($color-cyberspace, 70%);
    
    	@include m(hidden) {
    		transform: translateY(100%);
    		box-shadow: none;
    	}
    
    	@include e(player) {
    		display: flex;
    		flex-grow: 1;
    		align-items: center;
    
    		@include e(show-info) {
    			display: none;
    
    			@include bp-up(sm) {
    				display: block;
    			}
    		}
    
    		@include e(image) {
    			display: none;
    
    			@include bp-up(md) {
    				display: flex;
    				flex-grow: 0;
    				width: rhythm(7);
    				height: rhythm(7);
    				margin-right: rhythm(2);
    				border-radius: $border-radius;
    			}
    		}
    	}
    
    	@include e(show-info) {
    		display: flex;
    		flex-direction: column;
    	}
    
    	@include e(title) {
    		margin-bottom: 0;
    		color: $color-ruby;
    		font-family: $font-family-headings;
    		overflow: hidden;
    		text-overflow: ellipsis;
    		white-space: nowrap;
    		max-width: rem(200px);
    
    		@include bp-up(sm) {
    			max-width: rem(250px);
    		}
    
    		@include bp-up(lg) {
    			max-width: rem(500px);
    		}
    	}
    
    	@include e(description) {
    		display: block;
    		max-width: rem(200px);
    		overflow: hidden;
    		text-overflow: ellipsis;
    		white-space: nowrap;
    
    		> p {
    			display: none;
    		}
    
    		@include bp-up(sm) {
    			max-width: rem(250px);
    		}
    
    		@include bp-up(lg) {
    			max-width: rem(500px);
    		}
    	}
    
    	@include e(playtime) {
    		@include bp-between(sm, md) {
    			display: none;
    		}
    	}
    
    	@include e(spacer) {
    		@include bp-between(sm, md) {
    			display: none;
    		}
    	}
    
    	@include e(controls) {
    		display: flex;
    		flex-grow: 1;
    		align-items: center;
    		justify-content: center;
    	}
    
    	@include e(spacer) {
    		margin-right: rhythm(0.5);
    		margin-left: rhythm(0.5);
    	}
    
    	@include e(button) {
    		display: flex;
    		align-items: center;
    		margin: 0 rhythm(1);
    		padding: 0;
    		border: 0;
    		background-color: transparent;
    
    		&:hover {
    			> svg {
    				fill: $color-ruby;
    			}
    		}
    	}
    
    	@include e(timeleft) {
    		min-width: $time-left-width;
    	}
    
    	@include e(rewind-icon) {
    		width: $icon-size-large * 1.2;
    		height: $icon-size-large * 1.2;
    	}
    
    	@include e(forward-icon) {
    		width: $icon-size-large * 1.2;
    		height: $icon-size-large * 1.2;
    	}
    
    	@include e(play-icon) {
    		display: block;
    		width: $icon-size-large * 1.5;
    		height: $icon-size-large * 1.5;
    	}
    
    	@include e(progressbar) {
    		flex-wrap: wrap;
    		width: 100%;
    		height: rhythm(0.5);
    		margin-top: rhythm(1);
    		background-color: $color-concrete;
    
    		@include e(progress) {
    			width: 0;
    			height: rhythm(1);
    		}
    	}
    
    	@include e(close) {
    		position: absolute;
    		right: rhythm(0.5);
    		top: 50%;
    		transform: translateY(-50%);
    
    		@include bp-up(xxl) {
    			right: rhythm(2);
    		}
    	}
    }
    
  • URL: /components/raw/podcast/_podcast-player.scss
  • Filesystem Path: src/organisms/podcast/_podcast-player.scss
  • Size: 2.9 KB
  • Content:
    let namespace;
    const namespaceElement = document.querySelector('#site');
    const podCast = document.querySelector('.js-podcast');
    const audio = document.getElementById('podcastPlayer');
    const jsTrackList = document.querySelector('.js-track-list');
    const title = document.querySelector('.js-title');
    const description = document.querySelector('.js-description');
    const image = document.querySelector('.js-image');
    const progress = document.querySelector('.js-progress');
    const durationElement = document.querySelector('.js-duration');
    const timeleftElement = document.querySelector('.js-timeleft');
    const stepForward = document.querySelector('.js-step-forward');
    const stepBackward = document.querySelector('.js-step-backward');
    const playButton = document.querySelector('.js-play-button');
    const playIcon = document.querySelector('.js-play-icon');
    const pauseIcon = document.querySelector('.js-pause-icon');
    const closeButton = document.querySelector('.js-close-player');
    let rssURL = '';
    
    if (podCast) {
    	rssURL = podCast.dataset.rss;
    }
    
    if (!namespaceElement) {
    	namespace = '';
    } else {
    	namespace = namespaceElement.dataset.namespace;
    }
    
    function timeupdate() {
    	audio.addEventListener('timeupdate', () => {
    		const duration = parseInt(audio.duration, 10);
    		const currentTime = parseInt(audio.currentTime, 10);
    		const timeLeft = duration - currentTime;
    		let s; let
    			m;
    
    		s = timeLeft % 60;
    		m = Math.floor(timeLeft / 60) % 60;
    
    		s = s < 10 ? `0${s}` : s;
    		m = m < 10 ? `0${m}` : m;
    
    		if (timeLeft > 0) {
    			timeleftElement.classList.remove('u-visibility-hidden');
    			timeleftElement.innerHTML = `${m}:${s}`;
    		}
    	}, false);
    }
    
    let html = '';
    
    function getItems(el) {
    	html += `
    	<li>
    		<button
    			class="${namespace}o-podcast-player__button display-flex js-play-episode"
    			data-src="${el.querySelector('enclosure').getAttribute('url')}"
    			data-title="${el.querySelector('title').innerHTML}"
    			data-description="${el.querySelector('description').innerHTML.replace(/(<([^>]+)>)/gi, '').replace('<![CDATA[', '').replace(']]>', '')}"
    			data-image="${el.querySelector('image').getAttribute('href')}"
    			data-duration="${el.querySelector('duration').innerHTML}"
    			type="button"><svg class="icon ${namespace}o-podcast-player__play-icon u-m-r-2"><use xlink:href="#icon-play"></use></svg></div><div class="u-visuallyhidden">Spela avsnittet ${el.querySelector('title').innerHTML}</div></button>
    		<div class="${namespace}o-podcast-player__show-info">
    			<div class="${namespace}o-podcast-player__title">${el.querySelector('title').innerHTML}</div>
    			<div class="${namespace}o-podcast-player__description js-description">${el.querySelector('description').innerHTML}</div>
    		</div>
    	</li>
    `;
    
    	if (jsTrackList) {
    		jsTrackList.innerHTML = html;
    	}
    }
    
    if (rssURL) {
    	fetch(rssURL)
    		.then((response) => response.text())
    		.then((str) => new window.DOMParser().parseFromString(str, 'text/xml'))
    		.then((data) => {
    			const items = data.querySelectorAll('item');
    
    			items.forEach(getItems);
    		});
    }
    
    function playEpisode(playBtn) {
    	audio.src = playBtn.dataset.src;
    	durationElement.innerHTML = playBtn.dataset.duration;
    	title.innerHTML = playBtn.dataset.title;
    	description.innerHTML = playBtn.dataset.description;
    	image.src = playBtn.dataset.image;
    	podCast.classList.remove(`${namespace}o-podcast-player--hidden`);
    	timeleftElement.classList.add('u-visibility-hidden');
    
    	if (audio.play) {
    		audio.pause();
    		pauseIcon.classList.remove('is-hidden');
    		playIcon.classList.add('is-hidden');
    
    		audio.currentTime = 0;
    	}
    
    	audio.play();
    	timeupdate();
    
    	setTimeout(() => {
    		timeleftElement.classList.remove('u-visibility-hidden');
    	}, 1000);
    }
    
    document.body.addEventListener('click', (e) => {
    	const playBtn = e.target.closest('.js-play-episode');
    	if (playBtn) {
    		e.preventDefault();
    
    		// Clear old episodedata
    		audio.currentTime = 0;
    		timeupdate();
    		audio.pause();
    		sessionStorage.removeItem('episodeData');
    
    		// Play new episode
    		playEpisode(playBtn);
    	}
    });
    
    if (playButton) {
    	playButton.addEventListener('click', () => {
    		if (audio.paused) {
    			audio.muted = false;
    			audio.play();
    			pauseIcon.classList.remove('is-hidden');
    			playIcon.classList.add('is-hidden');
    			timeleftElement.classList.add('u-visibility-hidden');
    			timeupdate();
    			timeleftElement.classList.remove('u-visibility-hidden');
    		} else {
    			audio.pause();
    			pauseIcon.classList.add('is-hidden');
    			playIcon.classList.remove('is-hidden');
    		}
    	});
    }
    
    if (audio) {
    	audio.onended = () => {
    		pauseIcon.classList.add('is-hidden');
    		playIcon.classList.remove('is-hidden');
    		timeleftElement.classList.add('u-visibility-hidden');
    	};
    
    	audio.ontimeupdate = () => {
    		const timer = `${(audio.currentTime / audio.duration) * 100}%`;
    		progress.style.width = timer;
    	};
    }
    
    if (stepForward) {
    	stepForward.addEventListener('click', () => {
    		audio.currentTime += 60;
    		timeupdate();
    	});
    }
    
    if (stepBackward) {
    	stepBackward.addEventListener('click', () => {
    		audio.currentTime -= 15;
    		timeupdate();
    	});
    }
    
    function saveState() {
    	const podcastData = {
    		podCastTitle: title.innerHTML,
    		episodeDescription: description.innerHTML,
    		episodeSrc: audio.src,
    		episodeCurrentTime: audio.currentTime,
    		episodeDuration: durationElement.innerHTML,
    		episodeImage: image.src,
    	};
    
    	sessionStorage.setItem('episodeData', JSON.stringify(podcastData));
    
    	if (!audio.paused) {
    		let existing = sessionStorage.getItem('episodeData');
    		existing = existing ? JSON.parse(existing) : {};
    		existing.podcastWasPlaying = true;
    		sessionStorage.setItem('episodeData', JSON.stringify(existing));
    	} else {
    		let existing = sessionStorage.getItem('episodeData');
    		existing = existing ? JSON.parse(existing) : {};
    		existing.podcastWasPlaying = false;
    		sessionStorage.setItem('episodeData', JSON.stringify(existing));
    	}
    }
    
    // Handle continous play when user leaves the page
    if (podCast) {
    	window.addEventListener('visibilitychange', saveState);
    	window.addEventListener('beforeunload', saveState);
    }
    
    if (sessionStorage.getItem('episodeData') && podCast) {
    	const arr = JSON.parse(sessionStorage.getItem('episodeData'));
    
    	if (arr.episodeCurrentTime) {
    		podCast.classList.remove(`${namespace}o-podcast-player--hidden`);
    		audio.src = arr.episodeSrc;
    		image.src = arr.episodeImage;
    		title.innerHTML = arr.podCastTitle;
    		description.innerHTML = arr.episodeDescription;
    		durationElement.innerHTML = arr.episodeDuration;
    
    		if (arr.podcastWasPlaying === true) {
    			const playPromise = audio.play();
    
    			if (playPromise !== undefined) {
    				playPromise.then(() => {
    					// Continue playing audio on reload
    					audio.currentTime = arr.episodeCurrentTime;
    					timeupdate();
    					audio.play();
    					pauseIcon.classList.remove('is-hidden');
    					playIcon.classList.add('is-hidden');
    				}).catch(() => {
    					// User reloaded page manually. Cannot play audio
    					audio.addEventListener('loadedmetadata', () => {
    						audio.currentTime = arr.episodeCurrentTime;
    						timeupdate();
    					});
    
    					pauseIcon.classList.add('is-hidden');
    					playIcon.classList.remove('is-hidden');
    					audio.pause();
    				});
    			}
    		} else {
    			audio.addEventListener('loadedmetadata', () => {
    				audio.currentTime = arr.episodeCurrentTime;
    				timeupdate();
    			});
    			pauseIcon.classList.add('is-hidden');
    			playIcon.classList.remove('is-hidden');
    			audio.pause();
    		}
    	}
    }
    
    if (closeButton) {
    	closeButton.addEventListener('click', () => {
    		audio.currentTime = 0;
    		timeupdate();
    		audio.pause();
    		sessionStorage.removeItem('episodeData');
    		podCast.classList.add(`${namespace}o-podcast-player--hidden`);
    	});
    }
    
  • URL: /components/raw/podcast/podcast.js
  • Filesystem Path: src/organisms/podcast/podcast.js
  • Size: 7.6 KB

No notes defined.