diff --git a/src/AudioPlayerStyles.js b/src/AudioPlayerStyles.js
index 88c5adb..8df9511 100644
--- a/src/AudioPlayerStyles.js
+++ b/src/AudioPlayerStyles.js
@@ -1,113 +1,117 @@
const defaultClasses = {
- playerHeader: '',
- playerContainer: 'w-full mx-auto max-w-7xl',
- bibleListContainer: '',
- chapterListContainer: 'relative w-full max-w-4xl mx-auto',
- bibleListGrid: 'grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3',
+ playerHeader: '',
+ playerContainer: 'w-full mx-auto max-w-7xl',
+ bibleListContainer: '',
+ chapterListContainer: 'relative w-full max-w-4xl mx-auto',
+ bibleListGrid: 'grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3',
- bibleBlockInfoWrap: 'flex flex-row justify-between gap-4 max-w-md w-full mx-auto bg-stone-100 border border-stone-200 mt-12 p-3',
- bibleBlock: 'flex flex-col justify-between text-sm text-center md:text-base lg:text-lg font-semibold text-black',
- bibleBlockTitleGroup: 'flex-shrink truncate',
- bibleBlockVernacular: 'truncate text-sm mb-1',
- bibleBlockTitle: 'truncate text-2xl font-semibold',
- bibleBlockLanguageGroup: '',
- bibleBlockIso: 'text-sm',
- bibleBlockButtonGroup: 'flex flex-col justify-between',
+ bibleBlockInfoWrap: 'flex flex-row justify-between gap-4 max-w-md w-full mx-auto bg-stone-100 border border-stone-200 mt-12 p-3',
+ bibleBlock: 'flex flex-col justify-between text-sm text-center md:text-base lg:text-lg font-semibold text-black',
+ bibleBlockTitleGroup: 'flex-shrink truncate',
+ bibleBlockVernacular: 'truncate text-sm mb-1',
+ bibleBlockTitle: 'truncate text-2xl font-semibold',
+ bibleBlockLanguageGroup: '',
+ bibleBlockIso: 'text-sm',
+ bibleBlockButtonGroup: 'flex flex-col justify-between',
- container: 'audio-player-container mx-auto max-w-7xl w-full',
- audio: '',
- controlsContainer: 'controls-container flex flex-col',
- navRow: 'flex flex-row items-center justify-center gap-4 py-4 bg-stone-100 border border-stone-200 w-full max-w-md mx-auto',
- prevBookButton: 'prev-book-button flex justify-center items-center',
- prevChapterButton: 'prev-chapter-button flex justify-center items-center',
- playPauseButton: 'play-pause-button flex justify-center items-center',
- playPauseIcon: 'icon-play',
- nextChapterButton: 'next-chapter-button flex justify-center items-center',
- nextBookButton: 'next-book-button flex justify-center items-center',
- controlRow2: 'grid grid-cols-5 mb-1',
- leftControls: 'flex flex-row justify-center items-center col-span-2 space-x-6',
+ container: 'audio-player-container mx-auto max-w-7xl w-full',
+ audio: '',
+ controlsContainer: 'controls-container flex flex-col',
+ navRow: 'flex flex-row items-center justify-center gap-4 py-4 bg-stone-100 border border-stone-200 w-full max-w-md mx-auto',
+ prevBookButton: 'prev-book-button flex justify-center items-center',
+ prevChapterButton: 'prev-chapter-button flex justify-center items-center',
+ playPauseButton: 'play-pause-button flex justify-center items-center',
+ playPauseIcon: 'icon-play',
+ nextChapterButton: 'next-chapter-button flex justify-center items-center',
+ nextBookButton: 'next-book-button flex justify-center items-center',
+ controlRow2: 'grid grid-cols-5 mb-1',
+ leftControls: 'flex flex-row justify-center items-center col-span-2 space-x-6',
- playbackRate: {
- wrapper: 'flex mx-auto w-full max-w-md justify-center px-8 py-2 bg-stone-100 text-xs',
- display: 'mx-1 text-stone-400 text-sm',
- increase: 'w-6 h-6 border-2 rounded-2xl text-stone-500',
- decrease: 'w-6 h-6 border-2 rounded-2xl text-stone-500',
- disabled: 'opacity-50'
- },
+ playbackRate: {
+ wrapper: 'flex mx-auto w-full max-w-md justify-center px-8 py-2 bg-stone-100 text-xs',
+ display: 'mx-1 text-stone-400 text-sm',
+ increase: 'w-6 h-6 border-2 rounded-2xl text-stone-500',
+ decrease: 'w-6 h-6 border-2 rounded-2xl text-stone-500',
+ disabled: 'opacity-50'
+ },
- rightControls: 'flex flex-row justify-center items-center col-span-2 space-x-6',
- playSpeedControl: 'flex flex-row justify-center items-center text-xs',
- decrementIcon: 'size-6',
- incrementIcon: 'size-6',
- decreaseSpeedButton: 'w-6 h-6 border-2 rounded-2xl hover:border-blue-600 dark:hover:border-white text-stone-500 dark:hover:text-white',
- increaseSpeedButton: 'w-6 h-6 border-2 rounded-2xl hover:border-blue-600 dark:hover:border-white text-stone-500 dark:hover:text-white',
- prevSkipButton: 'flex justify-center items-center text-stone-400 hover:text-blue-600 dark:text-white dark:hover:text-blue-400',
- nextSkipButton: 'flex justify-center items-center text-stone-400 hover:text-blue-600 dark:text-white dark:hover:text-blue-400',
+ rightControls: 'flex flex-row justify-center items-center col-span-2 space-x-6',
+ playSpeedControl: 'flex flex-row justify-center items-center text-xs',
+ decrementIcon: 'size-6',
+ incrementIcon: 'size-6',
+ decreaseSpeedButton: 'w-6 h-6 border-2 rounded-2xl hover:border-blue-600 dark:hover:border-white text-stone-500 dark:hover:text-white',
+ increaseSpeedButton: 'w-6 h-6 border-2 rounded-2xl hover:border-blue-600 dark:hover:border-white text-stone-500 dark:hover:text-white',
+ prevSkipButton: 'flex justify-center items-center text-stone-400 hover:text-blue-600 dark:text-white dark:hover:text-blue-400',
+ nextSkipButton: 'flex justify-center items-center text-stone-400 hover:text-blue-600 dark:text-white dark:hover:text-blue-400',
- progress: {
- container: 'flex mx-auto w-full max-w-md justify-around px-1 pt-4 bg-stone-100 text-xs',
- wrapper: 'mx-auto min-w-80 mx-3 w-3/4 sm:w-5/6 h-3 rounded-lg relative border bg-gradient-to-br shadow-inner border-stone-300 from-white to-stone-200 dark:from-neutral-50 dark:to-stone-400 border-stone-500',
- barInner: 'h-3 w-0 bg-blue-600 rounded-md',
- circleTip: 'absolute top-1/2 transform -translate-y-1/2 -translate-x-1/2 w-5 h-5 bg-blue-600 rounded-full border border-blue-500',
- currentTimeDisplay: 'text-black',
- durationDisplay: 'text-black',
- tick: 'w-[1.5px] absolute top-0 h-full bg-stone-400'
- },
-
- mediaPlayerWrap: 'w-full flex flex-col justify-center',
- mediaPlayerHeader: '',
- mediaPlayerBody: '',
- mediaPlayerNavRow: 'grid grid-cols-5 px-2 text-stone-700 dark:text-stone-300 divide-x divide-stone-500 dark:divide-stone-600',
- bibleListNavButton: '',
- bookListNavButton: '',
- chapterListNavButton: '',
+ progress: {
+ container: 'flex flex-col mx-auto w-full max-w-md justify-around px-1 pt-4 bg-stone-100 text-xs',
+ barContainer: 'flex flex-row justify-between',
+ barWrapper: 'mx-auto min-w-80 mx-3 w-3/4 sm:w-5/6 h-3 rounded-lg relative border bg-gradient-to-br shadow-inner border-stone-300 from-white to-stone-200 dark:from-neutral-50 dark:to-stone-400 border-stone-500',
+ barInner: 'h-3 w-0 bg-blue-600 rounded-md',
+ circleTip: 'absolute top-1/2 transform -translate-y-1/2 -translate-x-1/2 w-5 h-5 bg-blue-600 rounded-full border border-blue-500',
+ currentTimeDisplay: 'text-black',
+ durationDisplay: 'text-black',
+ timestamps: 'flex w-full h-4 relative block my-4',
+ tick: 'absolute top-0 h-full bg-stone-400',
+ tickWrapper: 'group relative h-full',
+ tickLabel: 'verse-label hidden group-hover:block absolute left-1/2 -translate-x-1/2 top-full mt-1 px-2 py-1 text-xs bg-gray-200 text-gray-900 rounded-lg shadow border whitespace-nowrap'
+ },
+
+ mediaPlayerWrap: 'w-full flex flex-col justify-center',
+ mediaPlayerHeader: '',
+ mediaPlayerBody: '',
+ mediaPlayerNavRow: 'grid grid-cols-5 px-2 text-stone-700 dark:text-stone-300 divide-x divide-stone-500 dark:divide-stone-600',
+ bibleListNavButton: '',
+ bookListNavButton: '',
+ chapterListNavButton: '',
- volumeRow: 'flex mx-auto w-full max-w-md justify-center items-center px-8 bg-stone-100 text-xs',
- volumeControl: 'volume-control w-full',
- volumeInput: 'w-full',
- volumeLabel: 'flex flex-row pl-2 mx-4 w-1/2',
+ volumeRow: 'flex mx-auto w-full max-w-md justify-center items-center px-8 bg-stone-100 text-xs',
+ volumeControl: 'volume-control w-full',
+ volumeInput: 'w-full',
+ volumeLabel: 'flex flex-row pl-2 mx-4 w-1/2',
- selectBookChapterWrap: 'relative bg-stone-100 flex flex-row gap-2 w-full max-w-md mx-auto',
- selectBook: 'inline-flex items-center gap-x-1.5 bg-stone-100 hover:bg-stone-200 px-3 py-2 w-full',
- selectChapter: 'inline-flex items-center text-center bg-stone-100 w-12 hover:bg-stone-200 border-none appearance-none',
- selectVerseSeparator: 'inline-flex items-center bg-stone-100',
- selectVerse: "inline-flex items-center text-center bg-stone-100 w-12 hover:bg-stone-200 border-none appearance-none",
+ selectBookChapterWrap: 'relative bg-stone-100 flex flex-row gap-2 w-full max-w-md mx-auto',
+ selectBook: 'inline-flex items-center gap-x-1.5 bg-stone-100 hover:bg-stone-200 px-3 py-2 w-full',
+ selectChapter: 'inline-flex items-center text-center bg-stone-100 w-12 hover:bg-stone-200 border-none appearance-none',
+ selectVerseSeparator: 'inline-flex items-center bg-stone-100',
+ selectVerse: "inline-flex items-center text-center bg-stone-100 w-12 hover:bg-stone-200 border-none appearance-none",
- bibleButton: {
- wrapper: 'relative bg-stone-100 border border-stone-200 rounded min-h-20',
- button: 'flex flex-row bg-stone-100 h-full w-full hover:bg-stone-200',
- languageWrap: 'py-1 bg-stone-200 h-full w-24 flex flex-col justify-center items-center',
- language: 'text-sm font-medium text-stone-900',
- iso: 'truncate font-mono mt-1 text-sm text-stone-500',
- titleWrap: 'py-1 h-full w-full flex flex-col justify-center items-center',
- title: 'line-clamp-2 text-center text-sm font-medium text-stone-900',
- vernacular: 'text-sm text-stone-500 max-w-64',
- download: 'absolute text-stone-500 right-0 bottom-0 rounded-tl size-8 flex justify-center items-center bg-stone-200 hover:bg-stone-300'
- },
+ bibleButton: {
+ wrapper: 'relative bg-stone-100 border border-stone-200 rounded min-h-20',
+ button: 'flex flex-row bg-stone-100 h-full w-full hover:bg-stone-200',
+ languageWrap: 'py-1 bg-stone-200 h-full w-24 flex flex-col justify-center items-center',
+ language: 'text-sm font-medium text-stone-900',
+ iso: 'truncate font-mono mt-1 text-sm text-stone-500',
+ titleWrap: 'py-1 h-full w-full flex flex-col justify-center items-center',
+ title: 'line-clamp-2 text-center text-sm font-medium text-stone-900',
+ vernacular: 'text-sm text-stone-500 max-w-64',
+ download: 'absolute text-stone-500 right-0 bottom-0 rounded-tl size-8 flex justify-center items-center bg-stone-200 hover:bg-stone-300'
+ },
- bibleDownloadDialog: {
- wrapper: 'p-4 shadow-lg text-center',
- button_download: 'px-5 py-2 mx-1 bg-blue-500 text-white rounded cursor-pointer',
- cancel: 'px-5 py-2 mx-1 bg-red-600 text-white rounded cursor-pointer',
- audio_copyright: 'block text-stone-600',
- text_copyright: 'block text-stone-600 text-sm',
- },
+ bibleDownloadDialog: {
+ wrapper: 'p-4 shadow-lg text-center',
+ button_download: 'px-5 py-2 mx-1 bg-blue-500 text-white rounded cursor-pointer',
+ cancel: 'px-5 py-2 mx-1 bg-red-600 text-white rounded cursor-pointer',
+ audio_copyright: 'block text-stone-600',
+ text_copyright: 'block text-stone-600 text-sm',
+ },
- sleepTimerButton: 'sleep-timer-button group block py-1.5 px-1',
- sleepTimerDuration: 'text-xs pl-1 pt-2 inline-block timer-display',
- sleepTimerWrap: 'flex flex-row justify-center align-start text-stone-400 hover:text-blue-600',
- searchWrapper: 'mx-auto flex items-center w-1/3 my-3',
- searchInput: 'px-4 py-2 w-full max-w-7xl mx-auto bg-white border border-stone-200',
-
- searchInputContainer: 'flex w-full max-w-xl mx-auto my-4',
- bookListContainer: 'w-full',
- bookListGrid: 'grid grid-cols-3 md:grid-cols-4 gap-2',
- bookListButton: 'bg-stone-100 border rounded md:rounded-lg',
- bookListButtonActive: 'bg-stone-100 border border-stone-300',
- bookListTitle: 'font-medium text-sm lg:text-base',
- bookListId: 'w-full flex justify-end opacity-60 text-sm mr-8',
- chapterButton: 'bg-stone-100/90 w-12 h-12 m-2 border border-stone-700 rounded text-lg hover:bg-stone-300',
- chapterButtonActive: 'bg-stone-500 text-stone-100'
+ sleepTimerButton: 'sleep-timer-button group block py-1.5 px-1',
+ sleepTimerDuration: 'text-xs pl-1 pt-2 inline-block timer-display',
+ sleepTimerWrap: 'flex flex-row justify-center align-start text-stone-400 hover:text-blue-600',
+ searchWrapper: 'mx-auto flex items-center w-1/3 my-3',
+ searchInput: 'px-4 py-2 w-full max-w-7xl mx-auto bg-white border border-stone-200',
+
+ searchInputContainer: 'flex w-full max-w-xl mx-auto my-4',
+ bookListContainer: 'w-full',
+ bookListGrid: 'grid grid-cols-3 md:grid-cols-4 gap-2',
+ bookListButton: 'bg-stone-100 border rounded md:rounded-lg',
+ bookListButtonActive: 'bg-stone-100 border border-stone-300',
+ bookListTitle: 'font-medium text-sm lg:text-base',
+ bookListId: 'w-full flex justify-end opacity-60 text-sm mr-8',
+ chapterButton: 'bg-stone-100/90 w-12 h-12 m-2 border border-stone-700 rounded text-lg hover:bg-stone-300',
+ chapterButtonActive: 'bg-stone-500 text-stone-100'
};
/**
@@ -116,54 +120,54 @@ const defaultClasses = {
* Keys without an underscore will simply merge by replacing the existing default with the custom class.
*
* @param {Object} customClasses - An object containing custom class definitions. Keys starting with
- * an underscore indicate classes that should overwrite default classes.
+ * an underscore indicate classes that should overwrite default classes.
* @returns {Object} An object containing the merged classes.
*/
export const mergeClasses = (customClasses = {}) => {
- const mergedClasses = { ...defaultClasses };
- for (const key of Object.keys(customClasses)) {
- if (key.startsWith('_')) {
- mergedClasses[key.substring(1)] = customClasses[key];
- } else {
- mergedClasses[key] = customClasses[key];
- }
+ const mergedClasses = { ...defaultClasses };
+ for (const key of Object.keys(customClasses)) {
+ if (key.startsWith('_')) {
+ mergedClasses[key.substring(1)] = customClasses[key];
+ } else {
+ mergedClasses[key] = customClasses[key];
}
+ }
- return mergedClasses;
+ return mergedClasses;
};
const defaultIcons = {
- speedSlow: ``,
- speedFast: ``,
- sleepTimer: ``,
- chapters: ``,
- bibles: ``,
- books: ``,
- prevBook: ``,
- nextBook: ``,
- prevChapter: ``,
- nextChapter: ``,
- prevSkip: ``,
- nextSkip: ``,
- play: ``,
- pause: ``,
- volumeIcon: ``,
- download: ``
+ speedSlow: ``,
+ speedFast: ``,
+ sleepTimer: ``,
+ chapters: ``,
+ bibles: ``,
+ books: ``,
+ prevBook: ``,
+ nextBook: ``,
+ prevChapter: ``,
+ nextChapter: ``,
+ prevSkip: ``,
+ nextSkip: ``,
+ play: ``,
+ pause: ``,
+ volumeIcon: ``,
+ download: ``
}
export const mergeIcons = (customIcons = {}) => {
- const mergedIcons = { ...defaultIcons };
- for (const key of Object.keys(customIcons)) {
- mergedIcons[key] = customIcons[key];
- }
- return mergedIcons;
+ const mergedIcons = { ...defaultIcons };
+ for (const key of Object.keys(customIcons)) {
+ mergedIcons[key] = customIcons[key];
+ }
+ return mergedIcons;
};
const defaultArt = {}
export const mergeArt = (customArt = {}) => {
- const mergedArt = { ...defaultArt };
- for (const key of Object.keys(customArt)) {
- mergedArt[key] = customArt[key];
- }
- return mergedArt;
+ const mergedArt = { ...defaultArt };
+ for (const key of Object.keys(customArt)) {
+ mergedArt[key] = customArt[key];
+ }
+ return mergedArt;
};
\ No newline at end of file
diff --git a/src/MediaProgressBar.js b/src/MediaProgressBar.js
index 93cb562..576536f 100644
--- a/src/MediaProgressBar.js
+++ b/src/MediaProgressBar.js
@@ -14,8 +14,10 @@ export function createProgressBar(ctx) {
textContent: formatTime(ctx.audio.currentTime)
});
- const progressWrapper = elem('div', { id: 'progress-wrapper', className: ctx.class?.progress?.wrapper });
+ const progressBarContainer = elem('div', { id: 'progress-barContainer', className: ctx.class?.progress?.barContainer });
+ const progressWrapper = elem('div', { id: 'progress-barWrapper', className: ctx.class?.progress?.barWrapper });
const progressBarInner = elem('div', { id: 'progress-bar', className: ctx.class?.progress?.barInner, style: { width: '0%' } });
+
const timestampWrapper = elem('div', {id: 'timestamps-wrapper', className:ctx.class?.progress?.timestamps})
const circleTip = elem('div', {
@@ -65,26 +67,38 @@ export function createProgressBar(ctx) {
ctx.audio.addEventListener('loadedmetadata', () => {
timestampWrapper.innerHTML = '';
durationDisplay.textContent = formatTime(ctx.audio.duration);
-
+
if (ctx.currentChapter.timestamps && Array.isArray(ctx.currentChapter.timestamps)) {
+ const sortedTimestamps = ctx.currentChapter.timestamps.map(parseTimestampToSeconds).filter(ts => ts >= 0 && ts <= ctx.audio.duration).sort((a, b) => a - b);
+ const margin = .25;
+
+ for (let i = 0; i < sortedTimestamps.length - 1; i++) {
+ const current = sortedTimestamps[i];
+ const next = sortedTimestamps[i + 1];
+ const gapPercent = ((next - current) / ctx.audio.duration) * 100;
+ const adjustedWidth = Math.max(0, gapPercent - margin);
+ const adjustedLeft = ((current / ctx.audio.duration) * 100) + margin / 2;
+ const tickWrapper = elem('div',{className: ctx.class.tickWrapper,style:{position: 'absolute', left: `${adjustedLeft}%`, width: `${adjustedWidth}%`}});
+
+ const tick = elem('div', {className: `progress-tick ${ctx?.class?.progress?.tick} w-full h-full`});
+ tick.addEventListener('click', () => {ctx.audio.currentTime = current;});
+
+ const verseLabel = elem('div', {className: ctx.class.progress.tickLabel});
+ verseLabel.textContent = `${i + 1}`;
+
+ tickWrapper.appendChild(tick);
+ tickWrapper.appendChild(verseLabel);
+ timestampWrapper.appendChild(tickWrapper);
+ }
+ }
+ });
+
- ctx.currentChapter.timestamps.forEach(tsStr => {
- const tsSeconds = parseTimestampToSeconds(tsStr);
- if (tsSeconds <= ctx.audio.duration && tsSeconds >= 0) {
- const tickPercent = (tsSeconds / ctx.audio.duration) * 100;
- const tick = elem('div', {
- className: `progress-tick ${ctx?.class?.progress?.tick}`,
- style: { left: `${tickPercent}%` }
- });
- timestampWrapper.appendChild(tick);
- }
- });
- }
- });
-
- progressWrapper.append(timestampWrapper)
- progressContainer.append(currentTimeDisplay, progressWrapper, durationDisplay);
+ progressBarContainer.append(currentTimeDisplay)
+ progressBarContainer.append(progressWrapper)
+ progressBarContainer.append(durationDisplay)
+ progressContainer.append(progressBarContainer, timestampWrapper);
return progressContainer;
}