@fortawesome/free-solid-svg-icons#faCamera JavaScript Examples

The following examples show how to use @fortawesome/free-solid-svg-icons#faCamera. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Editor.js    From Reactive with MIT License 4 votes vote down vote up
// https://github.com/ffmpegwasm/ffmpeg.wasm/blob/master/docs/api.md


function Editor({videoUrl, timings, setTimings}) {

	//Boolean state to handle video mute
	const [isMuted, setIsMuted] = useState(false)

	//Boolean state to handle whether video is playing or not
	const [playing, setPlaying] = useState(false)

	//Float integer state to help with trimming duration logic
	const [difference, setDifference] = useState(0.2)

	//Boolean state to handle deleting grabber functionality
	const [deletingGrabber, setDeletingGrabber] = useState(false)

	//State for error handling
	const [currentWarning, setCurrentWarning] = useState(null)

	//State for imageUrl
	const [imageUrl, setImageUrl] = useState('')

	//Boolean state handling trimmed video
	const [trimmingDone, setTrimmingDone] = useState(false)

	//Integer state to blue progress bar as video plays
	const [seekerBar, setSeekerBar] = useState(0)


	//Ref handling metadata needed for trim markers
	const currentlyGrabbedRef = useRef({'index': 0, 'type': 'none'})

	//Ref handling the trimmed video element
	const trimmedVidRef = useRef()

	//Ref handling the initial video element for trimming
	const playVideoRef = useRef()

	//Ref handling the progress bar element
	const progressBarRef = useRef()

	//Ref handling the element of the current play time
	const playBackBarRef = useRef()

	//Variable for error handling on the delete grabber functionality
	const warnings = {'delete_grabber': (<div>Please click on the grabber (either start or end) to delete it</div>)}

	//State handling storing of the trimmed video
	const [trimmedVideo, setTrimmedVideo] = useState()

	//Integer state to handle the progress bars numerical incremation
	const [progress, setProgress] = useState(0)

	//Boolean state handling whether ffmpeg has loaded or not
	const [ready, setReady] = useState(false)

	//Ref to handle the current instance of ffmpeg when loaded
	const ffmpeg = useRef(null)


	//Function handling loading in ffmpeg
	const load = async () => {
		try{
			await ffmpeg.current.load()

			setReady(true)
		}
		catch(error) {
			console.log(error)
		}
	}

	//Loading in ffmpeg when this component renders
	useEffect(() => {
		ffmpeg.current = createFFmpeg({
			log: true,
			corePath: 'https://unpkg.com/@ffmpeg/[email protected]/dist/ffmpeg-core.js'
		})
		load()
	// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [])


	//Lifecycle handling the logic needed for the progress bar - displays the blue bar that grows as the video plays
	// eslint-disable-next-line react-hooks/exhaustive-deps
	useEffect(() => {
		if(playVideoRef.current.onloadedmetadata) {
			const currentIndex = currentlyGrabbedRef.current.index
			const seek = (playVideoRef.current.currentTime - timings[0].start) / playVideoRef.current.duration * 100
			setSeekerBar(seek)
			progressBarRef.current.style.width = `${seekerBar}%`
			if((playVideoRef.current.currentTime >= timings[0].end)) {
				playVideoRef.current.pause()
				setPlaying(false)
				currentlyGrabbedRef.current = ({'index': currentIndex + 1, 'type': 'start'})
				progressBarRef.current.style.width = '0%'
				progressBarRef.current.style.left = `${timings[0].start / playVideoRef.current.duration * 100}%`
				playVideoRef.current.currentTime = timings[0].start
			}
		}

		window.addEventListener('keyup', (event) => {
			if(event.key === ' ') {
				playPause()
			}
		})

		//Handles the start and end metadata for the timings state
		const time = timings
		playVideoRef.current.onloadedmetadata = () => {
			if(time.length === 0) {
				time.push({'start': 0, 'end': playVideoRef.current.duration})
				setTimings(time)
				addActiveSegments()
			}
			else{
				addActiveSegments()
			}
		}
	})

	//Lifecycle that handles removing event listener from the mouse event on trimmer - Desktop browser
	useEffect(() => {
		return window.removeEventListener('mouseup', removeMouseMoveEventListener)
	// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [])

	//Lifecycle that handles removing event listener from the touch/pointer event on trimmer - mobile browser
	useEffect(() => {
		return window.removeEventListener('pointerup', removePointerMoveEventListener)
	// eslint-disable-next-line react-hooks/exhaustive-deps
	}, [])

	//Function handling the trimmer movement logic
	const handleMouseMoveWhenGrabbed = (event) => {
		playVideoRef.current.pause()
		addActiveSegments()
		let playbackRect = playBackBarRef.current.getBoundingClientRect()
		let seekRatio = (event.clientX - playbackRect.left) / playbackRect.width
		const index = currentlyGrabbedRef.current.index
		const type = currentlyGrabbedRef.current.type
		let time = timings
		let seek = playVideoRef.current.duration * seekRatio
		if((type === 'start') && (seek > ((index !== 0) ? (time[index - 1].end + difference + 0.2) : 0)) && seek < time[index].end - difference){
			progressBarRef.current.style.left = `${seekRatio * 100}%`
			playVideoRef.current.currentTime = seek
			time[index]['start'] = seek
			setPlaying(false)
			setTimings(time)
		}
		else if((type === 'end') && (seek > time[index].start + difference) && (seek < (index !== (timings.length - 1) ? time[index].start - difference - 0.2 : playVideoRef.current.duration))){
			progressBarRef.current.style.left = `${time[index].start / playVideoRef.current.duration * 100}%`
			playVideoRef.current.currentTime = time[index].start
			time[index]['end'] = seek
			setPlaying(false)
			setTimings(time)
		}
		progressBarRef.current.style.width = '0%'
	}

	//Function that handles removing event listener from the mouse event on trimmer - Desktop browser
	const removeMouseMoveEventListener = () => {
		window.removeEventListener('mousemove', handleMouseMoveWhenGrabbed)
	}

	//Lifecycle that handles removing event listener from the mouse event on trimmer - Mobile browser
	const removePointerMoveEventListener = () => {
		window.removeEventListener('pointermove', handleMouseMoveWhenGrabbed)
	}

	//Function handling reset logic
	const reset = () => {
		playVideoRef.current.pause()

		setIsMuted(false)
		setPlaying(false)
		currentlyGrabbedRef.current = {'index': 0, 'type': 'none'}
		setDifference(0.2)
		setDeletingGrabber(false)
		setCurrentWarning(false)
		setImageUrl('')

		setTimings([{'start': 0, 'end': playVideoRef.current.duration}])
		playVideoRef.current.currentTime = timings[0].start
		progressBarRef.current.style.left = `${timings[0].start / playVideoRef.current.duration * 100}%`
		progressBarRef.current.style.width = '0%'
		addActiveSegments()
	}

	//Function handling thumbnail logic
	const captureSnapshot = () => {
		let video = playVideoRef.current
		const canvas = document.createElement('canvas')
		// scale the canvas accordingly
		canvas.width = video.videoWidth
		canvas.height = video.videoHeight
		// draw the video at that frame
		canvas.getContext('2d')
			.drawImage(video, 0, 0, canvas.width, canvas.height)
		// convert it to a usable data URL
		const dataURL = canvas.toDataURL()
		setImageUrl({imageUrl: dataURL})
	}

	//Function handling download of thumbnail logic
	const downloadSnapshot = () => {
		let a = document.createElement('a') //Create <a>
		a.href = imageUrl //Image Base64 Goes here
		a.download = 'Thumbnail.png' //File name Here
		a.click() //Downloaded file
	}

	//Function handling skip to previous logic
	const skipPrevious = () => {
		if(playing){
			playVideoRef.current.pause()
		}
		// let previousIndex = (currentlyGrabbed.index !== 0) ? (currentlyGrabbed.index - 1) : (timings.length - 1)
		// setCurrentlyGrabbed({currentlyGrabbed: {'index': previousIndex, 'type': 'start'}, playing: false})
		// currentlyGrabbedRef.current = {'index': previousIndex, 'type': 'start'}
		// progressBarRef.current.style.left = `${timings[previousIndex].start / playVideoRef.current.duration * 100}%`
		// progressBarRef.current.style.width = '0%'
		// playVideoRef.current.currentTime = timings[previousIndex].start
	}

	//Function handling play and pause logic
	const playPause = () => {
		if(playing){
			playVideoRef.current.pause()
		}
		else{
			if((playVideoRef.current.currentTime >= timings[0].end)) {
				playVideoRef.current.pause()
				setPlaying(false)
				currentlyGrabbedRef.current = {'index': 0, 'type': 'start'}
				playVideoRef.current.currentTime = timings[0].start
				progressBarRef.current.style.left = `${timings[0].start / playVideoRef.current.duration * 100}%`
				progressBarRef.current.style.width = '0%'
			}
			playVideoRef.current.play()
		}
		setPlaying(!playing)
	}

	//Function handling skip to next logic
	const skipNext = () => {
		if(playing){
			playVideoRef.current.pause()
		}
		// let nextIndex = (currentlyGrabbed.index !== (timings.length - 1)) ? (currentlyGrabbed.index + 1) : 0
		// setCurrentlyGrabbed({currentlyGrabbed: {'index': nextIndex, 'type': 'start'}, playing: false})
		// currentlyGrabbedRef.current = {'index': nextIndex, 'type': 'start'}
		// progressBarRef.current.style.left = `${timings[nextIndex].start / playVideoRef.current.duration * 100}%`
		// progressBarRef.current.style.width = '0%'
		// playVideoRef.current.currentTime = timings[nextIndex].start
	}

	//Function handling updating progress logic (clicking on progress bar to jump to different time durations)
	const updateProgress = (event) => {
		let playbackRect = playBackBarRef.current.getBoundingClientRect()
		let seekTime = ((event.clientX - playbackRect.left) / playbackRect.width) * playVideoRef.current.duration
		playVideoRef.current.pause()
		// find where seekTime is in the segment
		let index = -1
		let counter = 0
		for(let times of timings){
			if(seekTime >= times.start && seekTime <= times.end){
				index = counter
			}
			counter += 1
		}
		if(index === -1) {
			return
		}
		setPlaying(false)
		currentlyGrabbedRef.current = {'index': index, 'type': 'start'}
		progressBarRef.current.style.width = '0%' // Since the width is set later, this is necessary to hide weird UI
		progressBarRef.current.style.left = `${timings[index].start / playVideoRef.current.duration * 100}%`
		playVideoRef.current.currentTime = seekTime
	}

	//Function handling adding new trim markers logic
	const addGrabber = () => {
		const time = timings
		const end = time[time.length - 1].end + difference
		setDeletingGrabber({deletingGrabber: false, currentWarning: null})
		if(end >= playVideoRef.current.duration){
			return
		}
		time.push({'start': end + 0.2, 'end': playVideoRef.current.duration})
		setTimings(time)
		addActiveSegments()
	}

	//Function handling first step of deleting trimmer
	const preDeleteGrabber = () => {
		if(deletingGrabber){
			setDeletingGrabber({deletingGrabber: false, currentWarning: null})
		}
		else{
			setDeletingGrabber({deletingGrabber: true, currentWarning: 'delete_grabber'})
		}
	}

	//Function handling deletion of trimmers logic
	const deleteGrabber = (index) => {
		let time = timings
		setDeletingGrabber({deletingGrabber: false, currentWarning: null, currentlyGrabbed: {'index': 0, 'type': 'start'}})
		setDeletingGrabber({deletingGrabber: false, currentWarning: null, currentlyGrabbed: {'index': 0, 'type': 'start'}})
		if(time.length === 1){
			return
		}
		time.splice(index, 1)
		progressBarRef.current.style.left = `${time[0].start / playVideoRef.current.duration * 100}%`
		playVideoRef.current.currentTime = time[0].start
		progressBarRef.current.style.width = '0%'
		addActiveSegments()
	}

	//Function handling logic of time segments throughout videos duration
	const addActiveSegments = () => {
		let colors = ''
		let counter = 0
		colors += `, rgb(240, 240, 240) 0%, rgb(240, 240, 240) ${timings[0].start / playVideoRef.current.duration * 100}%`
		for(let times of timings) {
			if(counter > 0) {
				colors += `, rgb(240, 240, 240) ${timings[counter].end / playVideoRef.current.duration * 100}%, rgb(240, 240, 240) ${times.start / playVideoRef.current.duration * 100}%`
			}
			colors += `, #ccc ${times.start / playVideoRef.current.duration * 100}%, #ccc ${times.end / playVideoRef.current.duration * 100}%`
			counter += 1
		}
		colors += `, rgb(240, 240, 240) ${timings[counter - 1].end / playVideoRef.current.duration * 100}%, rgb(240, 240, 240) 100%`
		playBackBarRef.current.style.background = `linear-gradient(to right${colors})`
	}

	// Function handling logic for post trimmed video
	const saveVideo = async(fileInput) => {
		let metadata = {
			'trim_times': timings,
			'mute': isMuted
		}
		console.log(metadata.trim_times)
		const trimStart = metadata.trim_times[0].start
		const trimEnd = metadata.trim_times[0].end

		const trimmedVideo = trimEnd - trimStart

		console.log('Trimmed Duration: ', trimmedVideo)
		console.log('Trim End: ', trimEnd)

		try{
			//Disabling new-cap for FS function
			// eslint-disable-next-line new-cap
			ffmpeg.current.FS('writeFile', 'myFile.mp4', await fetchFile(videoUrl))

			ffmpeg.current.setProgress(({ratio}) => {
				console.log('ffmpeg progress: ', ratio)
				if(ratio < 0) {
					setProgress(0)
				}
				setProgress(Math.round(ratio * 100))
			})

			await ffmpeg.current.run('-ss', `${trimStart}`, '-accurate_seek', '-i', 'myFile.mp4', '-to', `${trimmedVideo}`, '-codec', 'copy', 'output.mp4')

			//Disabling new-cap for FS function
			// eslint-disable-next-line new-cap
			const data = ffmpeg.current.FS('readFile', 'output.mp4')

			const url = URL.createObjectURL(new Blob([data.buffer], {type: 'video/mp4'}))

			setTrimmedVideo(url)
			setTrimmingDone(true)
			// setLottiePlaying(false)
		}
		catch(error) {
			console.log(error)
		}
	}

	return (
		<div className='wrapper'>
			{/* Video element for the trimmed video */}
			{trimmingDone ?
				<div
					style={{
						maxHeight: '100vh',
						marginTop: '50vh'
					}}>
					<video
						style={{
							width: '100%',
							marginTop: '100px',
							borderRadius: '20px',
							border: '4px solid #0072cf'
						}}
						ref={trimmedVidRef}
						controls
						autoload='metadata'
						onClick={() => console.log(trimmedVidRef.current.duration)}
					>
						<source src={trimmedVideo} type='video/mp4' />
					</video>
				</div>
				: null
			}
			{/* Main video element for the video editor */}
			<video className='video'
				autoload='metadata'
				muted={isMuted}
				ref={playVideoRef}
				onLoadedData={() => {
					console.log(playVideoRef)
					playPause()
				}}
				onClick={() => {
					playPause()
				}}
				onTimeUpdate={() => {
					setSeekerBar(progressBarRef.current.style.width)
				}}
			>
				<source src={videoUrl} type='video/mp4' />
			</video>
			<div className='playback'>
				{/* If there is an instance of the playVideoRef, render the trimmer markers */}
				{playVideoRef.current ?
					Array.from(timings).map((timing, index) => (
						<div key={index}
						>
							<div key={'grabber_' + index}>
								{/* Markup and logic for the start trim marker */}
								<div id='grabberStart' className='grabber start'
									style={{left: `${timings[0].start / playVideoRef.current.duration * 100}%`}}
									// Events for desktop - Start marker
									onMouseDown={(event) => {
										if(deletingGrabber){
											deleteGrabber(index)
										}
										else{
											currentlyGrabbedRef.current = {'index': index, 'type': 'start'}
											window.addEventListener('mousemove', handleMouseMoveWhenGrabbed)
											window.addEventListener('mouseup', removeMouseMoveEventListener)
										}
									}}
									//Events for mobile - Start marker
									onPointerDown={() => {
										if(deletingGrabber){
											deleteGrabber(index)
										}
										else{
											currentlyGrabbedRef.current = {'index': index, 'type': 'start'}
											window.addEventListener('pointermove', handleMouseMoveWhenGrabbed)
											window.addEventListener('pointerup', removePointerMoveEventListener)
										}
									}}
								>
									<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0' y='0' width='10' height='14' viewBox='0 0 10 14' xmlSpace='preserve'>
										<path className='st0' d='M1 14L1 14c-0.6 0-1-0.4-1-1V1c0-0.6 0.4-1 1-1h0c0.6 0 1 0.4 1 1v12C2 13.6 1.6 14 1 14zM5 14L5 14c-0.6 0-1-0.4-1-1V1c0-0.6 0.4-1 1-1h0c0.6 0 1 0.4 1 1v12C6 13.6 5.6 14 5 14zM9 14L9 14c-0.6 0-1-0.4-1-1V1c0-0.6 0.4-1 1-1h0c0.6 0 1 0.4 1 1v12C10 13.6 9.6 14 9 14z'/>
									</svg>
								</div>
								{/* Markup and logic for the end trim marker */}
								<div id='grabberEnd' className='grabber end'
									style={{left: `${timings[0].end / playVideoRef.current.duration * 100}%`}}
									//Events for desktop - End marker
									onMouseDown={(event) => {
										if(deletingGrabber){
											deleteGrabber(index)
										}
										else{
											currentlyGrabbedRef.current = {'index': index, 'type': 'end'}
											window.addEventListener('mousemove', handleMouseMoveWhenGrabbed)
											window.addEventListener('mouseup', removeMouseMoveEventListener)
										}
									}}
									//Events for mobile - End marker
									onPointerDown={() => {
										if(deletingGrabber){
											deleteGrabber(index)
										}
										else{
											currentlyGrabbedRef.current = {'index': index, 'type': 'end'}
											window.addEventListener('pointermove', handleMouseMoveWhenGrabbed)
											window.addEventListener('pointerup', removePointerMoveEventListener)
										}
									}}
								>
									<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0' y='0' width='10' height='14' viewBox='0 0 10 14' xmlSpace='preserve'>
										<path className='st0' d='M1 14L1 14c-0.6 0-1-0.4-1-1V1c0-0.6 0.4-1 1-1h0c0.6 0 1 0.4 1 1v12C2 13.6 1.6 14 1 14zM5 14L5 14c-0.6 0-1-0.4-1-1V1c0-0.6 0.4-1 1-1h0c0.6 0 1 0.4 1 1v12C6 13.6 5.6 14 5 14zM9 14L9 14c-0.6 0-1-0.4-1-1V1c0-0.6 0.4-1 1-1h0c0.6 0 1 0.4 1 1v12C10 13.6 9.6 14 9 14z'/>
									</svg>
								</div>
							</div>
						</div>
					))
					: []}
				<div className='seekable' ref={playBackBarRef} onClick={updateProgress}></div>
				<div className='progress' ref={progressBarRef}></div>
			</div>

			<div className='controls'>
				<div className='player-controls'>
					<button className='settings-control' title='Reset Video' onClick={reset}><FontAwesomeIcon icon={faSync} /></button>
					<button className='settings-control' title='Mute/Unmute Video' onClick={() => setIsMuted({isMuted: !isMuted})}>{isMuted ? <FontAwesomeIcon icon={faVolumeMute} /> : <FontAwesomeIcon icon={faVolumeUp} />}</button>
					<button className='settings-control' title='Capture Thumbnail' onClick={captureSnapshot}><FontAwesomeIcon icon={faCamera} /></button>
				</div>
				<div className='player-controls'>
					<button className='seek-start' title='Skip to previous clip' onClick={skipPrevious}><FontAwesomeIcon icon={faStepBackward} /></button>
					<button className='play-control' title='Play/Pause' onClick={playPause} >{playing ? <FontAwesomeIcon icon={faPause} /> : <FontAwesomeIcon icon={faPlay} /> }</button>
					<button className='seek-end' title='Skip to next clip' onClick={skipNext}><FontAwesomeIcon icon={faStepForward} /></button>
				</div>
				<div>
					<button title='Add grabber' className='trim-control margined' onClick={addGrabber}>Add <FontAwesomeIcon icon={faGripLinesVertical} /></button>
					<button title='Delete grabber' className='trim-control margined' onClick={preDeleteGrabber}>Delete <FontAwesomeIcon icon={faGripLinesVertical} /></button>
					<button title='Save changes' className='trim-control' onClick={saveVideo}>Save</button>
				</div>
			</div>
			{ready ?
				<div></div>
				:
				<div>Loading...</div>
			}
			{currentWarning != null ? <div className={'warning'}>{warnings[currentWarning]}</div> : ''}
			{(imageUrl !== '') ?
				<div className={'marginVertical'}>
					<img src={imageUrl} className={'thumbnail'} alt='Photos' />
					<div className='controls'>
						<div className='player-controls'>
							<button className='settings-control' title='Reset Video' onClick={downloadSnapshot}><FontAwesomeIcon icon={faDownload} /></button>
							<button className='settings-control' title='Save Video' onClick={() => {
								setImageUrl('')
							}}><FontAwesomeIcon icon={faEraser} /></button>
						</div>
					</div>
				</div>
				: ''}
		</div>
	)
}