From e948840159d396bf648261cb0a876070ed40d878 Mon Sep 17 00:00:00 2001 From: "Hong Jing (Jingles)" Date: Mon, 10 Jan 2022 15:43:33 +0800 Subject: [PATCH 01/16] cache opencnft --- src/database/collections.js | 135 ++++++++++++++++++++++++++++++++++++ src/store/collection/api.js | 49 ++++++++----- 2 files changed, 168 insertions(+), 16 deletions(-) diff --git a/src/database/collections.js b/src/database/collections.js index d5a9be3..589f241 100644 --- a/src/database/collections.js +++ b/src/database/collections.js @@ -53,3 +53,138 @@ export const setCollectionCreator = async (address, percentage, policyId) => { export const setCollectionStatus = async (verified: Boolean, policyId) => { await saveCollection({ verified }, policyId); }; + + +/* get opencnft */ + +export const saveOpencnft = async (data, key) => { + try { + if (data) { + const reference = doc(firestore, "opencnft", key); + await setDoc(reference, data, { merge: true }); + } + } catch (error) { + console.error( + `Unexpected error in saveCollection. [Message: ${error.message}]` + ); + throw error; + } +}; + +export const getOpencnftTopProjects = async (time) => { + try { + + let time_outdated = 60 * 60 * 1000; // every hour + let time_now = new Date().getTime(); + + const reference = doc(firestore, "opencnft", "_top_projects_"+time); + const snapshot = await getDoc(reference); + + let return_top_projects = false; + let backup_return_top_projects = false + + if (snapshot.exists()){ + let data = snapshot.data(); + // console.log("gotten data", data); + if((time_now - data.last_updated) < time_outdated){ + // console.log("get existing top projects", time_now, data.last_updated, (time_now - data.last_updated) < time_outdated); + return_top_projects = data.rankings; + }else{ + backup_return_top_projects = data.rankings; + } + } + + if(return_top_projects == false){ + + const top_projects = await fetch(`https://api.opencnft.io/1/rank?window=${time}` , {}) + .then((res) => res.json()) + .then((res) => { + return res.ranking; + }) + .catch((err) => { + return false; + }); + + if(top_projects){ + let to_db = { + last_updated: time_now, + rankings: top_projects + } + + await saveOpencnft(to_db, "_top_projects_"+time); + // console.log("updated top projects"); + return_top_projects = top_projects; + } + else if(backup_return_top_projects){ // for some reason opencnft is down, but lucky we have records in our own database + return_top_projects = backup_return_top_projects; + } + + } + return return_top_projects; + + } catch (error) { + console.error( + `Unexpected error in getOpencnft. [Message: ${error.message}]` + ); + throw error; + } +}; + +export const getOpencnftPolicy = async (policy_id) => { + try { + + let time_outdated = 60 * 60 * 1000; // every hour + let time_now = new Date().getTime(); + + const reference = doc(firestore, "opencnft", "policy_"+policy_id); + const snapshot = await getDoc(reference); + + let return_project_stats = false; + let backup_return_project_stats = false + + if (snapshot.exists()){ + let data = snapshot.data(); + // console.log("gotten data", data); + if((time_now - data.last_updated) < time_outdated){ + // console.log("get existing opencnft data", time_now, data.last_updated, (time_now - data.last_updated) < time_outdated); + return_project_stats = data.rankings; + }else{ + backup_return_project_stats = data.rankings; + } + } + + if(return_project_stats == false){ + + const project_stats = await fetch(`https://api.opencnft.io/1/policy/${policy_id}`, {}) + .then((res) => res.json()) + .then((res) => { + return res; + }) + .catch((err) => { + return false; + }); + + if(project_stats){ + let to_db = { + last_updated: time_now, + rankings: project_stats + } + + await saveOpencnft(to_db, "policy_"+policy_id); + // console.log("updated opencnft collection", policy_id); + return_project_stats = project_stats; + } + else if(backup_return_project_stats){ // for some reason opencnft is down, but lucky we have records in our own database + return_project_stats = backup_return_project_stats; + } + + } + return return_project_stats; + + } catch (error) { + console.error( + `Unexpected error in getOpencnft. [Message: ${error.message}]` + ); + throw error; + } +}; diff --git a/src/store/collection/api.js b/src/store/collection/api.js index b3edcec..a19616e 100644 --- a/src/store/collection/api.js +++ b/src/store/collection/api.js @@ -18,6 +18,12 @@ import { getLockedAssets, } from "../../database/assets"; +import { + getOpencnftTopProjects, + getOpencnftPolicy, +} from "../../database/collections"; + + export const load_collection = (callback) => async (dispatch) => { let all_collections = {}; @@ -237,26 +243,37 @@ export const asset_add_offer = export const opencnft_get_top_projects = (time, callback) => async (dispatch) => { - fetch("https://api.opencnft.io/1/rank?window=" + time, {}) - .then((res) => res.json()) - .then((res) => { - callback({ success: true, data: res.ranking }); - }) - .catch((err) => { - console.error(err); - }); + + let rankings = await getOpencnftTopProjects(time); + callback({ success: true, data: rankings }); + + // old - to be removed - query opencnft without caching + // fetch("https://api.opencnft.io/1/rank?window=" + time, {}) + // .then((res) => res.json()) + // .then((res) => { + // callback({ success: true, data: res.ranking }); + // }) + // .catch((err) => { + // console.error(err); + // }); }; export const opencnft_get_policy = (policy_id, callback) => async (dispatch) => { - fetch(`https://api.opencnft.io/1/policy/${policy_id}`, {}) - .then((res) => res.json()) - .then((res) => { - callback({ success: true, data: res }); - }) - .catch((err) => { - console.error(err); - }); + + let project_stats = await getOpencnftPolicy(policy_id); + callback({ success: true, data: project_stats }); + + // old - to be removed - query opencnft without caching + // fetch(`https://api.opencnft.io/1/policy/${policy_id}`, {}) + // .then((res) => res.json()) + // .then((res) => { + // console.log(res); + // callback({ success: true, data: res }); + // }) + // .catch((err) => { + // console.error(err); + // }); }; export const opencnft_get_asset_tx = From feff72b75467a975cc98a7908ffaf3fe85659ae3 Mon Sep 17 00:00:00 2001 From: "Hong Jing (Jingles)" Date: Mon, 10 Jan 2022 15:52:42 +0800 Subject: [PATCH 02/16] fix number converter --- src/utils/converter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/converter.js b/src/utils/converter.js index 40de54c..aec5475 100644 --- a/src/utils/converter.js +++ b/src/utils/converter.js @@ -77,11 +77,11 @@ export const get_asset_image_source = (image) => { }; export const numFormatter = (num) => { - if (num > 999 && num < 1000000) { + if (num >= 1000 && num < 1000000) { return (num / 1000).toFixed(1) + "K"; // convert to K for number from > 1000 < 1 million } else if (num > 1000000) { return (num / 1000000).toFixed(1) + "M"; // convert to M for number from > 1 million - } else if (num < 900) { + } else if (num < 1000) { return num; // if value < 1000, nothing to do } }; From e72a57d3bd98c16e50822b93ade87f88cceb399b Mon Sep 17 00:00:00 2001 From: "Hong Jing (Jingles)" Date: Mon, 10 Jan 2022 15:54:27 +0800 Subject: [PATCH 03/16] add number convert to all opencnft stats --- src/components/CollectionBanner/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/CollectionBanner/index.js b/src/components/CollectionBanner/index.js index 88183d3..a72f1ed 100644 --- a/src/components/CollectionBanner/index.js +++ b/src/components/CollectionBanner/index.js @@ -139,10 +139,11 @@ const CollectionBanner = ({thisCollection, size, asset, is_collection_page, is_a

Floor price

- ₳{ + ₳{numFormatter( thisCollection.opencnft.reduce(function (result, policy){ return Math.min(result, policy.floor_price) - },999999*1000000)/1000000}

+ },999999*1000000)/1000000) + }

@@ -151,10 +152,10 @@ const CollectionBanner = ({thisCollection, size, asset, is_collection_page, is_a

Total assets

- { + {numFormatter( thisCollection.opencnft.reduce(function (result, policy){ return result + policy.asset_minted - },0) + },0)) }

@@ -165,10 +166,10 @@ const CollectionBanner = ({thisCollection, size, asset, is_collection_page, is_a

Number owners

- { + {numFormatter( thisCollection.opencnft.reduce(function (result, policy){ return result + policy.asset_holders - },0) + },0)) }

From 588ed288ac0d8482033ca4df2962b125dda87662 Mon Sep 17 00:00:00 2001 From: "Hong Jing (Jingles)" Date: Tue, 11 Jan 2022 10:27:16 +0800 Subject: [PATCH 04/16] show collection for projects not available on opencnft --- src/hooks/usePolicyMetadatas.js | 13 ++++++++++--- src/pages/Collection/ListingSection.js | 2 +- src/pages/Collection/index.js | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/hooks/usePolicyMetadatas.js b/src/hooks/usePolicyMetadatas.js index 1ad0d7a..1eea503 100644 --- a/src/hooks/usePolicyMetadatas.js +++ b/src/hooks/usePolicyMetadatas.js @@ -17,19 +17,26 @@ export const usePolicyMetadatas = (policyIds) => { }) ); + let validMetadatas = []; + if (metadatas.length === 1) { if (metadatas[0].statusCode === 404) { console.log( "usePolicyMetadatas: this collection is not in opencnft", policyIds ); + validMetadatas.push({policy: policyIds[0]}); } } - const validMetadatas = metadatas.filter( - (metadata) => metadata.policy !== undefined - ); + if(validMetadatas.length==0){ + validMetadatas = metadatas.filter( + (metadata) => metadata.policy !== undefined + ); + } + setPolicyMetadatas(validMetadatas); + } catch (error) { // console.error( // `Unexpected error in usePolicyMetadatas. [Message: ${error.message}]` diff --git a/src/pages/Collection/ListingSection.js b/src/pages/Collection/ListingSection.js index 17d45ed..bf015aa 100644 --- a/src/pages/Collection/ListingSection.js +++ b/src/pages/Collection/ListingSection.js @@ -16,7 +16,7 @@ const ListingSection = ({ state_collection, policyIds }) => { const [lastVisible, setLastVisible] = useState(null); const [paginationObject, setPaginationObject] = useState(undefined); const [policyMetadatas, loadingData] = usePolicyMetadatas(policyIds); - + const resetComponentState = useCallback(() => { if (policyMetadatas.length > 0) { let tmpPaginationObject = {}; diff --git a/src/pages/Collection/index.js b/src/pages/Collection/index.js index 0d3d8eb..e66e221 100644 --- a/src/pages/Collection/index.js +++ b/src/pages/Collection/index.js @@ -62,7 +62,7 @@ const Collection = () => { if (currentCollectionIterator.hasOwnProperty("policy_id")) { setPolicyIds([currentCollectionIterator.policy_id]); } - + for (let policyIdx in policy_ids) { let policy_id = policy_ids[policyIdx]; dispatch( From 5b5ba301ddece9d09ce824584a71bd63c5f588c2 Mon Sep 17 00:00:00 2001 From: "Hong Jing (Jingles)" Date: Thu, 13 Jan 2022 22:12:17 +0800 Subject: [PATCH 05/16] audio player --- src/components/AssetImageFigure/index.js | 2 +- src/components/AudioPlayer/AudioControls.jsx | 56 +++++++ src/components/AudioPlayer/Backdrop.jsx | 11 ++ src/components/AudioPlayer/assets/next.svg | 3 + src/components/AudioPlayer/assets/pause.svg | 3 + src/components/AudioPlayer/assets/play.svg | 3 + src/components/AudioPlayer/assets/prev.svg | 3 + src/components/AudioPlayer/index.jsx | 150 +++++++++++++++++++ src/components/AudioPlayer/styles.css | 94 ++++++++++++ src/pages/Asset/index.js | 29 +++- 10 files changed, 346 insertions(+), 8 deletions(-) create mode 100644 src/components/AudioPlayer/AudioControls.jsx create mode 100644 src/components/AudioPlayer/Backdrop.jsx create mode 100644 src/components/AudioPlayer/assets/next.svg create mode 100644 src/components/AudioPlayer/assets/pause.svg create mode 100644 src/components/AudioPlayer/assets/play.svg create mode 100644 src/components/AudioPlayer/assets/prev.svg create mode 100644 src/components/AudioPlayer/index.jsx create mode 100644 src/components/AudioPlayer/styles.css diff --git a/src/components/AssetImageFigure/index.js b/src/components/AssetImageFigure/index.js index 4469eb4..e6d4398 100644 --- a/src/components/AssetImageFigure/index.js +++ b/src/components/AssetImageFigure/index.js @@ -9,7 +9,7 @@ const AssetImageFigure = ({ asset, setShow, show_trigger, width, no_figure, clas <> { no_figure ? ( - + {asset.details.onchainMetadata.name} ) : setShow ? (
setShow(show_trigger ? show_trigger : false)} style={{ cursor: "pointer" }}> diff --git a/src/components/AudioPlayer/AudioControls.jsx b/src/components/AudioPlayer/AudioControls.jsx new file mode 100644 index 0000000..b6102e9 --- /dev/null +++ b/src/components/AudioPlayer/AudioControls.jsx @@ -0,0 +1,56 @@ +import React from "react"; +import { ReactComponent as Play } from "./assets/play.svg"; +import { ReactComponent as Pause } from "./assets/pause.svg"; +// import { ReactComponent as Next } from "./assets/next.svg"; +// import { ReactComponent as Prev } from "./assets/prev.svg"; + +const AudioControls = ({ + isPlaying, + onPlayPauseClick, + onPrevClick, + onNextClick +}) => ( +
+ {/* */} + {isPlaying ? ( + + ) : ( + + )} + {/* */} +
+); + +export default AudioControls; diff --git a/src/components/AudioPlayer/Backdrop.jsx b/src/components/AudioPlayer/Backdrop.jsx new file mode 100644 index 0000000..5dd78f4 --- /dev/null +++ b/src/components/AudioPlayer/Backdrop.jsx @@ -0,0 +1,11 @@ +import React, { useEffect } from "react"; + +const Backdrop = ({ activeColor, trackIndex, isPlaying }) => { + useEffect(() => { + document.documentElement.style.setProperty("--active-color", activeColor); + }, [trackIndex, activeColor]); + + return
; +}; + +export default Backdrop; diff --git a/src/components/AudioPlayer/assets/next.svg b/src/components/AudioPlayer/assets/next.svg new file mode 100644 index 0000000..bb69b04 --- /dev/null +++ b/src/components/AudioPlayer/assets/next.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/AudioPlayer/assets/pause.svg b/src/components/AudioPlayer/assets/pause.svg new file mode 100644 index 0000000..c3f1866 --- /dev/null +++ b/src/components/AudioPlayer/assets/pause.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/AudioPlayer/assets/play.svg b/src/components/AudioPlayer/assets/play.svg new file mode 100644 index 0000000..f912f88 --- /dev/null +++ b/src/components/AudioPlayer/assets/play.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/AudioPlayer/assets/prev.svg b/src/components/AudioPlayer/assets/prev.svg new file mode 100644 index 0000000..d7a1140 --- /dev/null +++ b/src/components/AudioPlayer/assets/prev.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/AudioPlayer/index.jsx b/src/components/AudioPlayer/index.jsx new file mode 100644 index 0000000..6feff1c --- /dev/null +++ b/src/components/AudioPlayer/index.jsx @@ -0,0 +1,150 @@ +import React, { useState, useEffect, useRef } from "react"; +import AudioControls from "./AudioControls"; +import Backdrop from "./Backdrop"; +import "./styles.css"; +import { get_asset_image_source } from "../../utils/converter"; + +const AudioPlayer = ({ tracks }) => { + // State + const [trackIndex, setTrackIndex] = useState(0); + const [trackProgress, setTrackProgress] = useState(0); + const [isPlaying, setIsPlaying] = useState(false); + + // Destructure for conciseness + const { title, artist, color, image, audioSrc } = tracks[trackIndex]; + + // Refs + const audioRef = useRef(new Audio(audioSrc)); + const intervalRef = useRef(); + const isReady = useRef(false); + + // Destructure for conciseness + const { duration } = audioRef.current; + + const currentPercentage = duration + ? `${(trackProgress / duration) * 100}%` + : "0%"; + const trackStyling = ` + -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(${currentPercentage}, #fff), color-stop(${currentPercentage}, #777)) + `; + + const startTimer = () => { + // Clear any timers already running + clearInterval(intervalRef.current); + + intervalRef.current = setInterval(() => { + if (audioRef.current.ended) { + toNextTrack(); + } else { + setTrackProgress(audioRef.current.currentTime); + } + }, [1000]); + }; + + const onScrub = (value) => { + // Clear any timers already running + clearInterval(intervalRef.current); + audioRef.current.currentTime = value; + setTrackProgress(audioRef.current.currentTime); + }; + + const onScrubEnd = () => { + // If not already playing, start + if (!isPlaying) { + setIsPlaying(true); + } + startTimer(); + }; + + const toPrevTrack = () => { + if (trackIndex - 1 < 0) { + setTrackIndex(tracks.length - 1); + } else { + setTrackIndex(trackIndex - 1); + } + }; + + const toNextTrack = () => { + if (trackIndex < tracks.length - 1) { + setTrackIndex(trackIndex + 1); + } else { + setTrackIndex(0); + setIsPlaying(false); + } + }; + + useEffect(() => { + if (isPlaying) { + audioRef.current.play(); + startTimer(); + } else { + audioRef.current.pause(); + } + }, [isPlaying]); + + // Handles cleanup and setup when changing tracks + useEffect(() => { + audioRef.current.pause(); + + audioRef.current = new Audio(audioSrc); + setTrackProgress(audioRef.current.currentTime); + + if (isReady.current) { + audioRef.current.play(); + setIsPlaying(true); + startTimer(); + } else { + // Set the isReady ref as true for the next pass + isReady.current = true; + } + }, [trackIndex]); + + useEffect(() => { + // Pause and clean up on unmount + return () => { + audioRef.current.pause(); + clearInterval(intervalRef.current); + }; + }, []); + + return ( + //
+
+
+ {`track +

{title}

+ {/*

{artist}

*/} + + onScrub(e.target.value)} + onMouseUp={onScrubEnd} + onKeyUp={onScrubEnd} + style={{ background: trackStyling }} + /> +
+ +
+ ); +}; + +export default AudioPlayer; diff --git a/src/components/AudioPlayer/styles.css b/src/components/AudioPlayer/styles.css new file mode 100644 index 0000000..9a52956 --- /dev/null +++ b/src/components/AudioPlayer/styles.css @@ -0,0 +1,94 @@ +.audio-player { + max-width: 350px; + padding: 24px; + border-radius: 20px; + box-shadow: 0 28px 28px rgba(0, 0, 0, 0.2); + margin: auto; + color: var(--white); +} + +.audio-player Button { + background: none; + border: none; + cursor: pointer; + --white: #fff; + --active-color: #00aeb0; +} + +.audio-player input[type="range"] { + height: 5px; + -webkit-appearance: none; + width: 100%; + margin-bottom: 10px; + border-radius: 8px; + background: #3b7677; + transition: background 0.2s ease; + cursor: pointer; +} + +.audio-player .artwork { + border-radius: 120px; + display: block; + margin: auto; + height: 200px; + width: 200px; +} + +.audio-player .track-info { + text-align: center; + z-index: 1; + position: relative; +} + +.audio-player .title { + font-weight: 700; + margin-bottom: 4px; + color: #fff; +} + +.audio-player .artist { + font-weight: 300; + margin-top: 0; +} + +.audio-player .audio-controls { + display: flex; + justify-content: space-between; + width: 75%; + margin: 0 auto 15px; + justify-content: center; +} + +.audio-player .audio-controls .prev svg, +.audio-player .audio-controls .next svg { + width: 35px; + height: 35px; +} + +.audio-player .audio-controls .play svg, +.audio-player .audio-controls .pause svg { + height: 40px; + width: 40px; +} + +.audio-player .audio-controls path { + fill: var(--white); +} + +.audio-player.color-backdrop { + background: linear-gradient(45deg, var(--active-color) 100%, transparent 100%) no-repeat; + z-index: -1; +} + +.audio-player.color-backdrop.playing { + animation: colorChange 20s alternate infinite; +} + +@keyframes colorChange { + from { + filter: hue-rotate(0deg); + } + to { + filter: hue-rotate(360deg); + } +} diff --git a/src/pages/Asset/index.js b/src/pages/Asset/index.js index b86bc03..5a8b8cd 100644 --- a/src/pages/Asset/index.js +++ b/src/pages/Asset/index.js @@ -3,6 +3,7 @@ import { useSelector, useDispatch } from "react-redux"; import { Link, useParams } from "react-router-dom"; import Moment from "react-moment"; import SweetAlert from "react-bootstrap-sweetalert"; +import AudioPlayer from "../../components/AudioPlayer"; import { urls } from "config"; import { @@ -1067,7 +1068,7 @@ const AssetImage = ({ asset }) => { } }, [asset, show]); - + return (
@@ -1078,12 +1079,26 @@ const AssetImage = ({ asset }) => { contentType === "html" && contentSource ? : - // contentType == "audio" && contentSource ? - // - // : - + contentType == "audio" && contentSource ? +
+ +
+ : + <> +

+ +

+ }
*/} - {isPlaying ? ( - - ) : ( - - )} - {/* */} -
-); - -export default AudioControls; diff --git a/src/components/AudioPlayer/Backdrop.jsx b/src/components/AudioPlayer/Backdrop.jsx deleted file mode 100644 index 5dd78f4..0000000 --- a/src/components/AudioPlayer/Backdrop.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import React, { useEffect } from "react"; - -const Backdrop = ({ activeColor, trackIndex, isPlaying }) => { - useEffect(() => { - document.documentElement.style.setProperty("--active-color", activeColor); - }, [trackIndex, activeColor]); - - return
; -}; - -export default Backdrop; diff --git a/src/components/AudioPlayer/assets/next.svg b/src/components/AudioPlayer/assets/next.svg deleted file mode 100644 index bb69b04..0000000 --- a/src/components/AudioPlayer/assets/next.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/components/AudioPlayer/assets/pause.svg b/src/components/AudioPlayer/assets/pause.svg deleted file mode 100644 index c3f1866..0000000 --- a/src/components/AudioPlayer/assets/pause.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/components/AudioPlayer/assets/play.svg b/src/components/AudioPlayer/assets/play.svg deleted file mode 100644 index f912f88..0000000 --- a/src/components/AudioPlayer/assets/play.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/components/AudioPlayer/assets/prev.svg b/src/components/AudioPlayer/assets/prev.svg deleted file mode 100644 index d7a1140..0000000 --- a/src/components/AudioPlayer/assets/prev.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/components/AudioPlayer/index.jsx b/src/components/AudioPlayer/index.jsx deleted file mode 100644 index 6feff1c..0000000 --- a/src/components/AudioPlayer/index.jsx +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useState, useEffect, useRef } from "react"; -import AudioControls from "./AudioControls"; -import Backdrop from "./Backdrop"; -import "./styles.css"; -import { get_asset_image_source } from "../../utils/converter"; - -const AudioPlayer = ({ tracks }) => { - // State - const [trackIndex, setTrackIndex] = useState(0); - const [trackProgress, setTrackProgress] = useState(0); - const [isPlaying, setIsPlaying] = useState(false); - - // Destructure for conciseness - const { title, artist, color, image, audioSrc } = tracks[trackIndex]; - - // Refs - const audioRef = useRef(new Audio(audioSrc)); - const intervalRef = useRef(); - const isReady = useRef(false); - - // Destructure for conciseness - const { duration } = audioRef.current; - - const currentPercentage = duration - ? `${(trackProgress / duration) * 100}%` - : "0%"; - const trackStyling = ` - -webkit-gradient(linear, 0% 0%, 100% 0%, color-stop(${currentPercentage}, #fff), color-stop(${currentPercentage}, #777)) - `; - - const startTimer = () => { - // Clear any timers already running - clearInterval(intervalRef.current); - - intervalRef.current = setInterval(() => { - if (audioRef.current.ended) { - toNextTrack(); - } else { - setTrackProgress(audioRef.current.currentTime); - } - }, [1000]); - }; - - const onScrub = (value) => { - // Clear any timers already running - clearInterval(intervalRef.current); - audioRef.current.currentTime = value; - setTrackProgress(audioRef.current.currentTime); - }; - - const onScrubEnd = () => { - // If not already playing, start - if (!isPlaying) { - setIsPlaying(true); - } - startTimer(); - }; - - const toPrevTrack = () => { - if (trackIndex - 1 < 0) { - setTrackIndex(tracks.length - 1); - } else { - setTrackIndex(trackIndex - 1); - } - }; - - const toNextTrack = () => { - if (trackIndex < tracks.length - 1) { - setTrackIndex(trackIndex + 1); - } else { - setTrackIndex(0); - setIsPlaying(false); - } - }; - - useEffect(() => { - if (isPlaying) { - audioRef.current.play(); - startTimer(); - } else { - audioRef.current.pause(); - } - }, [isPlaying]); - - // Handles cleanup and setup when changing tracks - useEffect(() => { - audioRef.current.pause(); - - audioRef.current = new Audio(audioSrc); - setTrackProgress(audioRef.current.currentTime); - - if (isReady.current) { - audioRef.current.play(); - setIsPlaying(true); - startTimer(); - } else { - // Set the isReady ref as true for the next pass - isReady.current = true; - } - }, [trackIndex]); - - useEffect(() => { - // Pause and clean up on unmount - return () => { - audioRef.current.pause(); - clearInterval(intervalRef.current); - }; - }, []); - - return ( - //
-
-
- {`track -

{title}

- {/*

{artist}

*/} - - onScrub(e.target.value)} - onMouseUp={onScrubEnd} - onKeyUp={onScrubEnd} - style={{ background: trackStyling }} - /> -
- -
- ); -}; - -export default AudioPlayer; diff --git a/src/components/AudioPlayer/styles.css b/src/components/AudioPlayer/styles.css deleted file mode 100644 index 9a52956..0000000 --- a/src/components/AudioPlayer/styles.css +++ /dev/null @@ -1,94 +0,0 @@ -.audio-player { - max-width: 350px; - padding: 24px; - border-radius: 20px; - box-shadow: 0 28px 28px rgba(0, 0, 0, 0.2); - margin: auto; - color: var(--white); -} - -.audio-player Button { - background: none; - border: none; - cursor: pointer; - --white: #fff; - --active-color: #00aeb0; -} - -.audio-player input[type="range"] { - height: 5px; - -webkit-appearance: none; - width: 100%; - margin-bottom: 10px; - border-radius: 8px; - background: #3b7677; - transition: background 0.2s ease; - cursor: pointer; -} - -.audio-player .artwork { - border-radius: 120px; - display: block; - margin: auto; - height: 200px; - width: 200px; -} - -.audio-player .track-info { - text-align: center; - z-index: 1; - position: relative; -} - -.audio-player .title { - font-weight: 700; - margin-bottom: 4px; - color: #fff; -} - -.audio-player .artist { - font-weight: 300; - margin-top: 0; -} - -.audio-player .audio-controls { - display: flex; - justify-content: space-between; - width: 75%; - margin: 0 auto 15px; - justify-content: center; -} - -.audio-player .audio-controls .prev svg, -.audio-player .audio-controls .next svg { - width: 35px; - height: 35px; -} - -.audio-player .audio-controls .play svg, -.audio-player .audio-controls .pause svg { - height: 40px; - width: 40px; -} - -.audio-player .audio-controls path { - fill: var(--white); -} - -.audio-player.color-backdrop { - background: linear-gradient(45deg, var(--active-color) 100%, transparent 100%) no-repeat; - z-index: -1; -} - -.audio-player.color-backdrop.playing { - animation: colorChange 20s alternate infinite; -} - -@keyframes colorChange { - from { - filter: hue-rotate(0deg); - } - to { - filter: hue-rotate(360deg); - } -} diff --git a/src/components/VideoPlayer/index.jsx b/src/components/VideoPlayer/index.jsx new file mode 100644 index 0000000..3d4fd5b --- /dev/null +++ b/src/components/VideoPlayer/index.jsx @@ -0,0 +1,12 @@ +import videoConnect from 'react-html5video'; +import 'react-html5video/dist/styles.css'; + +const MyVideoPlayer = ({ video, videoEl, children, ...restProps }) => ( +
+ +
+); + +export default videoConnect(MyVideoPlayer) \ No newline at end of file diff --git a/src/pages/Asset/index.js b/src/pages/Asset/index.js index 4b64a17..6984952 100644 --- a/src/pages/Asset/index.js +++ b/src/pages/Asset/index.js @@ -1,9 +1,10 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useRef } from "react"; import { useSelector, useDispatch } from "react-redux"; import { Link, useParams } from "react-router-dom"; import Moment from "react-moment"; import SweetAlert from "react-bootstrap-sweetalert"; -import AudioPlayer from "../../components/AudioPlayer"; +import { DefaultPlayer as Video } from 'react-html5video'; +import 'react-html5video/dist/styles.css'; import { urls } from "config"; import { @@ -1045,6 +1046,7 @@ const AssetImage = ({ asset }) => { const [show, setShow] = useState(false); const [contentType, setContentType] = useState("image"); const [contentSource, setContentSource] = useState(null); + const videoPlayerRef = useRef(); useEffect(() => { // detect html, require cleaning @@ -1086,23 +1088,22 @@ const AssetImage = ({ asset }) => {
-
setShow(false)}>
+
{ setShow(false); if (videoPlayerRef != undefined) { + videoPlayerRef.current.videoEl.pause(); + videoPlayerRef.current.videoEl.currentTime = 0; + } } }>
{contentType === "html" && contentSource ? ( ) : contentType == "audio" && contentSource ? (
- +
) : ( <> @@ -1119,7 +1120,15 @@ const AssetImage = ({ asset }) => {
diff --git a/src/pages/Asset/style.css b/src/pages/Asset/style.css index 50f2fc2..798ef61 100644 --- a/src/pages/Asset/style.css +++ b/src/pages/Asset/style.css @@ -21,3 +21,7 @@ .asset .image img{ object-fit: contain; } + +.rh5v-Volume_icon { + padding: 4px; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 48f8e79..fef9648 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1033,7 +1033,7 @@ core-js-pure "^3.19.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.16.7" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz" integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== @@ -3834,6 +3834,11 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +classnames@^2.2.6: + version "2.3.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== + clean-css@^4.2.3: version "4.2.4" resolved "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz" @@ -9941,6 +9946,11 @@ react-helmet@^6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" +react-html5video@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/react-html5video/-/react-html5video-2.5.1.tgz#bbc4314be1d583cb991a11ca3480e70a3c964caf" + integrity sha1-u8QxS+HVg8uZGhHKNIDnCjyWTK8= + react-infinite-scroll-component@^6.1.0: version "6.1.0" resolved "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz" @@ -10196,7 +10206,7 @@ redux-thunk@^2.3.0: resolved "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz" integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== -redux@^4.0.0, redux@^4.1.1: +redux@^4.0.0, redux@^4.0.1, redux@^4.1.1: version "4.1.2" resolved "https://registry.npmjs.org/redux/-/redux-4.1.2.tgz" integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== @@ -12029,6 +12039,17 @@ vendors@^1.0.0: resolved "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz" integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== +video-react@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/video-react/-/video-react-0.15.0.tgz#82794a0f862e08c5f3ac2660747360f5ecc735cb" + integrity sha512-wF3BwG1qikkSX11nu0KsygxeWehzMaYpd4Uvy1sKLFnCNk794f9TPL4q/+mMmLJ8uYb5DSlgg6VraTHyihiMHQ== + dependencies: + "@babel/runtime" "^7.4.5" + classnames "^2.2.6" + lodash.throttle "^4.1.1" + prop-types "^15.7.2" + redux "^4.0.1" + vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz" From 93fa4d0d53dc39584ec08e5a666ef49bef57b68c Mon Sep 17 00:00:00 2001 From: Kazune Takeda <58710601+kazunetakeda25@users.noreply.github.com> Date: Wed, 19 Jan 2022 07:52:07 +0900 Subject: [PATCH 09/16] audio and video player - created different players for both audio / video - for audio, shows image behind the audio player component --- src/pages/Asset/index.js | 65 +++++++++++++++++++++++++++++++++------ src/pages/Asset/style.css | 9 +++++- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/pages/Asset/index.js b/src/pages/Asset/index.js index 6984952..3e1ae02 100644 --- a/src/pages/Asset/index.js +++ b/src/pages/Asset/index.js @@ -1046,7 +1046,7 @@ const AssetImage = ({ asset }) => { const [show, setShow] = useState(false); const [contentType, setContentType] = useState("image"); const [contentSource, setContentSource] = useState(null); - const videoPlayerRef = useRef(); + const playerRef = useRef(); useEffect(() => { // detect html, require cleaning @@ -1060,8 +1060,20 @@ const AssetImage = ({ asset }) => { thisContentType = "html"; } else if ( asset.details.onchainMetadata.files[0].mediaType == "audio/mpeg" + || asset.details.onchainMetadata.files[0].mediaType == "audio/wave" + || asset.details.onchainMetadata.files[0].mediaType == "audio/wav" + || asset.details.onchainMetadata.files[0].mediaType == "audio/x-wav" + || asset.details.onchainMetadata.files[0].mediaType == "audio/x-pn-wav" + || asset.details.onchainMetadata.files[0].mediaType == "audio/webm" + || asset.details.onchainMetadata.files[0].mediaType == "audio/ogg" ) { thisContentType = "audio"; + } else if ( + asset.details.onchainMetadata.files[0].mediaType == "video/mp4" + || asset.details.onchainMetadata.files[0].mediaType == "video/webm" + || asset.details.onchainMetadata.files[0].mediaType == "video/ogg" + ) { + thisContentType = "video"; } if (thisContentType != "image") { @@ -1088,16 +1100,43 @@ const AssetImage = ({ asset }) => {
-
{ setShow(false); if (videoPlayerRef != undefined) { - videoPlayerRef.current.videoEl.pause(); - videoPlayerRef.current.videoEl.currentTime = 0; - } } }>
+
{ + setShow(false); + if (playerRef.current != undefined) { + if (contentType == 'audio') { + playerRef.current.audioEl.pause(); + playerRef.current.audioEl.currentTime = 0; + } + } + if (playerRef.current != undefined) { + if (contentType == 'video') { + playerRef.current.videoEl.pause(); + playerRef.current.videoEl.currentTime = 0; + } + } + } + } + >
{contentType === "html" && contentSource ? ( ) : contentType == "audio" && contentSource ? (
-
+ ) : contentType == "video" && contentSource ? ( +
+