Timestamp (#430)

* Test

Messing around to figure out docker and whatnot

* Revert "Test"

This reverts commit 43b76932c5.

* Update docker dev yaml

Update the docker to the environement with the latest version of selenium (and their new arguments), and to be on firefox

* Timestamp on play

Adds the a one time event that jumps to a timestamp if there is a t parameter in the URL

* Timestamp when sharing DRAFT

First draft to show the timestamp when sharing. Missing checkbox and appending the get param to shown link

* Checkbox and update share url

Checkbox show a clean timestamp and the media URL updates with the correct timestamp upon selection

* Cleaning before PR

removing un-necessary modified  files

* Clean up before PR - remove statics

* Timestamp in comments

Parse the comments to wrap timestamps with an appropriate anchor

* Forgotten comments and console.logs

* Last touch for PR

- Cleaning media.js for PR
- Using  MediaPageStore instead of window.location when wrapping the timestamp in comments

* Screenshot

Adding the screenshot for the user_docs

* PR amends

Amending VideoPlayer componnent to take check if the Get param 't' is a number, and to keep it within the duration of the video.
Required to change the listener from 'play' to 'loadedmetadata' to have access to the video duration (otherwise it was too early)

Also changed the User_doc file to inform users of the timestamp function
This commit is contained in:
MrPercheul 2022-03-23 16:38:02 +01:00 committed by GitHub
parent fb0f3ee739
commit e7ae2833d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 115 additions and 4 deletions

BIN
docs/images/Demo1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

BIN
docs/images/Demo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
docs/images/Demo3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -5,6 +5,7 @@
- [Downloading media](#downloading-media) - [Downloading media](#downloading-media)
- [Adding captions/subtitles](#adding-captionssubtitles) - [Adding captions/subtitles](#adding-captionssubtitles)
- [Search media](#search-media) - [Search media](#search-media)
- [Using Timestamps for sharing](#-using-timestamp)
- [Share media](#share-media) - [Share media](#share-media)
- [Embed media](#embed-media) - [Embed media](#embed-media)
- [Customize my profile options](#customize-my-profile-options) - [Customize my profile options](#customize-my-profile-options)
@ -195,6 +196,30 @@ You can now watch the captions/subtitles play back in the video player - and tog
<img src="./images/CC-display.png"/> <img src="./images/CC-display.png"/>
</p> </p>
## Using Timestamps for sharing
### Using Timestamp in the URL
An additionnal Get parameter 't' can be added in video URL's to start the video at the given time. The starting time has to be given in seconds.
<p align="left">
<img src="./images/Demo1.png"/>
</p>
Additionnally the share button has an option to generate the URL with the timestamp at current second the video is.
<p align="left">
<img src="./images/Demo2.png"/>
</p>
### Using Timestamp in the comments
Comments can also include timestamps. They are automatically detected upon posting the comment, and will be in the form of an hyperlink link in the comment. The timestamps in the comments have to follow the format HH:MM:SS or MM:SS
<p align="left">
<img src="./images/Demo3.png"/>
</p>
## Search media ## Search media
How search can be used How search can be used

View File

@ -368,8 +368,36 @@ export default function CommentsList(props) {
const [displayComments, setDisplayComments] = useState(false); const [displayComments, setDisplayComments] = useState(false);
function onCommentsLoad() { function onCommentsLoad() {
const retrievedComments = [...MediaPageStore.get('media-comments')];
retrievedComments.forEach(comment => {
comment.text = setTimestampAnchors(comment.text);
});
displayCommentsRelatedAlert(); displayCommentsRelatedAlert();
setComments([...MediaPageStore.get('media-comments')]); setComments(retrievedComments);
}
function setTimestampAnchors(text)
{
function wrapTimestampWithAnchor(match, string)
{
let split = match.split(':'), s = 0, m = 1;
let searchParameters = new URLSearchParams(window.location.search);
while (split.length > 0)
{
s += m * parseInt(split.pop(), 10);
m *= 60;
}
searchParameters.set('t', s)
const wrapped = "<a href=\"" + MediaPageStore.get('media-url').split('?')[0] + "?" + searchParameters + "\">" + match + "</a>";
return wrapped;
}
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
return text.replace(timeRegex , wrapTimestampWithAnchor);
} }
function onCommentSubmit(commentId) { function onCommentSubmit(commentId) {

View File

@ -182,9 +182,27 @@ function updateDimensions() {
}; };
} }
function getTimestamp() {
const videoPlayer = document.getElementsByTagName("video");
return videoPlayer[0]?.currentTime;
}
function ToHHMMSS (timeInt) {
let sec_num = parseInt(timeInt, 10);
let hours = Math.floor(sec_num / 3600);
let minutes = Math.floor((sec_num - (hours * 3600)) / 60);
let seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) {hours = "0"+hours;}
if (minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds;}
return hours >= 1 ? hours + ':' + minutes + ':' + seconds : minutes + ':' + seconds;
}
export function MediaShareOptions(props) { export function MediaShareOptions(props) {
const containerRef = useRef(null); const containerRef = useRef(null);
const shareOptionsInnerRef = useRef(null); const shareOptionsInnerRef = useRef(null);
const mediaUrl = MediaPageStore.get('media-url');
const [inlineSlider, setInlineSlider] = useState(null); const [inlineSlider, setInlineSlider] = useState(null);
const [sliderButtonsVisible, setSliderButtonsVisible] = useState({ prev: false, next: false }); const [sliderButtonsVisible, setSliderButtonsVisible] = useState({ prev: false, next: false });
@ -192,6 +210,12 @@ export function MediaShareOptions(props) {
const [dimensions, setDimensions] = useState(updateDimensions()); const [dimensions, setDimensions] = useState(updateDimensions());
const [shareOptions] = useState(ShareOptions()); const [shareOptions] = useState(ShareOptions());
const [timestamp, setTimestamp] = useState(0);
const [formattedTimestamp, setFormattedTimestamp] = useState(0);
const [startAtSelected, setStartAtSelected] = useState(false);
const [shareMediaLink, setShareMediaLink] = useState(mediaUrl);
function onWindowResize() { function onWindowResize() {
setDimensions(updateDimensions()); setDimensions(updateDimensions());
} }
@ -219,6 +243,17 @@ export function MediaShareOptions(props) {
}); });
} }
function updateStartAtCheckbox() {
setStartAtSelected(!startAtSelected);
updateShareMediaLink();
}
function updateShareMediaLink()
{
const newLink = startAtSelected ? mediaUrl : mediaUrl + "&t=" + Math.trunc(timestamp);
setShareMediaLink(newLink);
}
function nextSlide() { function nextSlide() {
inlineSlider.nextSlide(); inlineSlider.nextSlide();
updateSlider(); updateSlider();
@ -244,6 +279,10 @@ export function MediaShareOptions(props) {
PageStore.on('window_resize', onWindowResize); PageStore.on('window_resize', onWindowResize);
MediaPageStore.on('copied_media_link', onCompleteCopyMediaLink); MediaPageStore.on('copied_media_link', onCompleteCopyMediaLink);
const localTimestamp = getTimestamp();
setTimestamp(localTimestamp);
setFormattedTimestamp(ToHHMMSS(localTimestamp));
return () => { return () => {
PageStore.removeListener('window_resize', onWindowResize); PageStore.removeListener('window_resize', onWindowResize);
MediaPageStore.removeListener('copied_media_link', onCompleteCopyMediaLink); MediaPageStore.removeListener('copied_media_link', onCompleteCopyMediaLink);
@ -273,10 +312,22 @@ export function MediaShareOptions(props) {
</div> </div>
<div className="copy-field"> <div className="copy-field">
<div> <div>
<input type="text" readOnly value={MediaPageStore.get('media-url')} /> <input type="text" readOnly value={shareMediaLink} />
<button onClick={onClickCopyMediaLink}>COPY</button> <button onClick={onClickCopyMediaLink}>COPY</button>
</div> </div>
</div> </div>
<div className="start-at">
<label>
<input
type="checkbox"
name="start-at-checkbox"
id="id-start-at-checkbox"
checked={startAtSelected}
onChange={updateStartAtCheckbox}
/>
Start at {formattedTimestamp}
</label>
</div>
</div> </div>
); );
} }

View File

@ -192,6 +192,13 @@ export function VideoPlayer(props) {
document.addEventListener('visibilitychange', initPlayer); document.addEventListener('visibilitychange', initPlayer);
} }
player.player.one('loadedmetadata', () => {
const urlParams = new URLSearchParams(window.location.search);
const paramT = Number(urlParams.get('t'));
const timestamp = !isNaN(paramT) ? paramT : 0;
player.player.currentTime(timestamp);
});
return () => { return () => {
unsetPlayer(); unsetPlayer();