Recherche avancée

Médias (0)

Mot : - Tags -/performance

Aucun média correspondant à vos critères n’est disponible sur le site.

Autres articles (56)

  • Websites made ​​with MediaSPIP

    2 mai 2011, par

    This page lists some websites based on MediaSPIP.

  • Creating farms of unique websites

    13 avril 2011, par

    MediaSPIP platforms can be installed as a farm, with a single "core" hosted on a dedicated server and used by multiple websites.
    This allows (among other things) : implementation costs to be shared between several different projects / individuals rapid deployment of multiple unique sites creation of groups of like-minded sites, making it possible to browse media in a more controlled and selective environment than the major "open" (...)

  • Support audio et vidéo HTML5

    10 avril 2011

    MediaSPIP utilise les balises HTML5 video et audio pour la lecture de documents multimedia en profitant des dernières innovations du W3C supportées par les navigateurs modernes.
    Pour les navigateurs plus anciens, le lecteur flash Flowplayer est utilisé.
    Le lecteur HTML5 utilisé a été spécifiquement créé pour MediaSPIP : il est complètement modifiable graphiquement pour correspondre à un thème choisi.
    Ces technologies permettent de distribuer vidéo et son à la fois sur des ordinateurs conventionnels (...)

Sur d’autres sites (8696)

  • Error initializing FFmpegKit : "TypeError : Cannot read property 'getLogLevel' of null" in React Native

    9 janvier, par Md Monirozzaman khan

    I'm developing a React Native application where I need to process videos using the ffmpeg-kit-react-native library. However, I'm encountering an issue during the initialization of FFmpegKitConfig. The error message is :

    


    ERROR  Error initializing FFmpegKit: [TypeError: Cannot read property 'getLogLevel' of null]


    


    Here is my App.js code

    


    

    

    import React, { useState, useEffect } from &#x27;react&#x27;;&#xA;import { StyleSheet, Text, View, TouchableOpacity, Alert, Dimensions, ScrollView, LayoutAnimation, UIManager, Platform } from &#x27;react-native&#x27;;&#xA;import * as ImagePicker from &#x27;expo-image-picker&#x27;;&#xA;import * as FileSystem from &#x27;expo-file-system&#x27;;&#xA;import { Video } from &#x27;expo-av&#x27;;&#xA;import { MaterialIcons } from &#x27;@expo/vector-icons&#x27;;&#xA;import { FFmpegKit, FFmpegKitConfig, ReturnCode } from &#x27;ffmpeg-kit-react-native&#x27;;&#xA;&#xA;const windowWidth = Dimensions.get(&#x27;window&#x27;).width;&#xA;&#xA;if (Platform.OS === &#x27;android&#x27;) {&#xA;  UIManager.setLayoutAnimationEnabledExperimental &amp;&amp; UIManager.setLayoutAnimationEnabledExperimental(true);&#xA;}&#xA;&#xA;export default function App() {&#xA;  const [videoFiles, setVideoFiles] = useState([]);&#xA;  const [isGridView, setIsGridView] = useState(false);&#xA;  const [isConverting, setIsConverting] = useState(false);&#xA;&#xA;  useEffect(() => {&#xA;    FFmpegKitConfig.init()&#xA;      .then(() => {&#xA;        console.log(&#x27;FFmpegKit initialized&#x27;);&#xA;      })&#xA;      .catch((error) => {&#xA;        console.error(&#x27;Error initializing FFmpegKit:&#x27;, error);&#xA;      });&#xA;  }, []);&#xA;&#xA;  const pickVideo = async () => {&#xA;    const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();&#xA;    if (status !== &#x27;granted&#x27;) {&#xA;      alert(&#x27;Sorry, we need media library permissions to make this work!&#x27;);&#xA;      return;&#xA;    }&#xA;&#xA;    let result = await ImagePicker.launchImageLibraryAsync({&#xA;      mediaTypes: ImagePicker.MediaTypeOptions.Videos,&#xA;      allowsMultipleSelection: true,&#xA;    });&#xA;&#xA;    if (!result.canceled &amp;&amp; result.assets.length > 0) {&#xA;      const newFiles = result.assets.filter(&#xA;        (newFile) => !videoFiles.some((existingFile) => existingFile.uri === newFile.uri)&#xA;      );&#xA;&#xA;      if (newFiles.length &lt; result.assets.length) {&#xA;        Alert.alert(&#x27;Duplicate Files&#x27;, &#x27;Some files were already added.&#x27;);&#xA;      }&#xA;&#xA;      LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);&#xA;      setVideoFiles([...videoFiles, ...newFiles]);&#xA;    }&#xA;  };&#xA;&#xA;  const convertVideos = async () => {&#xA;    setIsConverting(true);&#xA;    const outputDir = `${FileSystem.documentDirectory}Output`;&#xA;&#xA;    const dirInfo = await FileSystem.getInfoAsync(outputDir);&#xA;    if (!dirInfo.exists) {&#xA;      await FileSystem.makeDirectoryAsync(outputDir, { intermediates: true });&#xA;    }&#xA;&#xA;    for (const video of videoFiles) {&#xA;      const { uri } = video;&#xA;      const filename = uri.split(&#x27;/&#x27;).pop();&#xA;      const outputFilePath = `${outputDir}/${filename.split(&#x27;.&#x27;).slice(0, -1).join(&#x27;.&#x27;)}_modified.mp4`;&#xA;&#xA;      const ffmpegCommand = `-y -i "${uri}" -af "atempo=1.02, bass=g=4:f=80:w=3, treble=g=4:f=3200:w=3, firequalizer=gain_entry=&#x27;entry(0,0);entry(62,2);entry(125,1.5);entry(250,1);entry(500,1);entry(1000,1);entry(2000,1.5);entry(4000,2.5);entry(8000,3);entry(16000,4)&#x27;, compand=attacks=0.05:decays=0.25:points=-80/-80-50/-15-30/-10-10/-2:soft-knee=4:gain=2, deesser, highpass=f=35, lowpass=f=17000, loudnorm=I=-16:LRA=11:TP=-1.5, volume=3.9dB" -c:v copy -c:a aac -b:a 224k -ar 48000 -threads 0 "${outputFilePath}"`;&#xA;&#xA;      try {&#xA;        const session = await FFmpegKit.execute(ffmpegCommand);&#xA;        const returnCode = await session.getReturnCode();&#xA;&#xA;        if (ReturnCode.isSuccess(returnCode)) {&#xA;          console.log(`Video converted: ${outputFilePath}`);&#xA;        } else if (ReturnCode.isCancel(returnCode)) {&#xA;          console.log(&#x27;Conversion cancelled&#x27;);&#xA;        } else {&#xA;          console.error(`FFmpeg process failed: ${session.getFailStackTrace()}`);&#xA;        }&#xA;      } catch (error) {&#xA;        console.error(`Error converting video: ${error.message}`);&#xA;      }&#xA;    }&#xA;&#xA;    setIsConverting(false);&#xA;    Alert.alert(&#x27;Conversion Complete&#x27;, &#x27;All videos have been converted.&#x27;);&#xA;  };&#xA;&#xA;  const deleteVideo = (uri) => {&#xA;    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);&#xA;    setVideoFiles(videoFiles.filter((video) => video.uri !== uri));&#xA;  };&#xA;&#xA;  const clearAllVideos = () => {&#xA;    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);&#xA;    setVideoFiles([]);&#xA;  };&#xA;&#xA;  const toggleLayout = () => {&#xA;    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);&#xA;    setIsGridView(!isGridView);&#xA;  };&#xA;&#xA;  return (&#xA;    <view style="{styles.container}">&#xA;      <text style="{styles.header}">Video Converter App</text>&#xA;      <touchableopacity style="{styles.addButton}">&#xA;        <text style="{styles.addButtonText}">Select or Browse Videos</text>&#xA;      </touchableopacity>&#xA;      <view style="{styles.headerContainer}">&#xA;        <text style="{styles.videoCount}">Total Videos: {videoFiles.length}</text>&#xA;        {videoFiles.length > 0 &amp;&amp; (&#xA;          &lt;>&#xA;            <touchableopacity style="{styles.clearButtonContainer}">&#xA;              <materialicons size="{24}" color="red" style="{styles.clearIcon}"></materialicons>&#xA;              <text style="{styles.clearAllText}">Clear All</text>&#xA;            </touchableopacity>&#xA;            <touchableopacity style="{styles.toggleLayoutButton}">&#xA;              <materialicons size="{24}" color="#fff"></materialicons>&#xA;            </touchableopacity>&#xA;          >&#xA;        )}&#xA;      </view>&#xA;      {isGridView ? (&#xA;        <scrollview contentcontainerstyle="{styles.gridContainer}">&#xA;          {videoFiles.map((item, index) => (&#xA;            <view key="{index}" style="{styles.videoItemGrid}">&#xA;              &#xA;              <touchableopacity>> deleteVideo(item.uri)} style={styles.deleteButtonGrid}>&#xA;                <materialicons size="{24}" color="red"></materialicons>&#xA;              </touchableopacity>&#xA;            </view>&#xA;          ))}&#xA;        </scrollview>&#xA;      ) : (&#xA;        <view style="{styles.list}">&#xA;          {videoFiles.map((item, index) => (&#xA;            <view key="{index}" style="{styles.videoItem}">&#xA;              &#xA;              <text style="{styles.fileName}">{decodeURI(item.fileName || item.uri.split(&#x27;/&#x27;).pop() || &#x27;Unknown File&#x27;)}</text>&#xA;              <touchableopacity>> deleteVideo(item.uri)} style={styles.deleteButton}>&#xA;                <materialicons size="{24}" color="red"></materialicons>&#xA;              </touchableopacity>&#xA;            </view>&#xA;          ))}&#xA;        </view>&#xA;      )}&#xA;      {videoFiles.length > 0 &amp;&amp; (&#xA;        <touchableopacity style="{styles.convertButton}" disabled="{isConverting}">&#xA;          <text style="{styles.convertButtonText}">{isConverting ? &#x27;Converting...&#x27; : &#x27;Convert&#x27;}</text>&#xA;        </touchableopacity>&#xA;      )}&#xA;    </view>&#xA;  );&#xA;}&#xA;&#xA;const styles = StyleSheet.create({&#xA;  container: {&#xA;    flex: 1,&#xA;    backgroundColor: &#x27;#fff&#x27;,&#xA;    alignItems: &#x27;center&#x27;,&#xA;    padding: 10,&#xA;  },&#xA;  header: {&#xA;    fontSize: 24,&#xA;    fontWeight: &#x27;bold&#x27;,&#xA;    marginBottom: 5,&#xA;  },&#xA;  addButton: {&#xA;    backgroundColor: &#x27;#007BFF&#x27;,&#xA;    padding: 15,&#xA;    borderRadius: 10,&#xA;    alignItems: &#x27;center&#x27;,&#xA;    marginBottom: 5,&#xA;    width: &#x27;100%&#x27;,&#xA;    elevation: 2,&#xA;    shadowColor: &#x27;#000&#x27;,&#xA;    shadowOffset: { width: 0, height: 5 },&#xA;    shadowOpacity: 0.8,&#xA;    shadowRadius: 2,&#xA;  },&#xA;  addButtonText: {&#xA;    color: &#x27;#fff&#x27;,&#xA;    fontSize: 18,&#xA;    fontWeight: &#x27;bold&#x27;,&#xA;  },&#xA;  headerContainer: {&#xA;    flexDirection: &#x27;row&#x27;,&#xA;    alignItems: &#x27;center&#x27;,&#xA;    justifyContent: &#x27;space-between&#x27;,&#xA;    width: &#x27;100%&#x27;,&#xA;    marginBottom: 10,&#xA;  },&#xA;  videoCount: {&#xA;    fontSize: 18,&#xA;  },&#xA;  clearButtonContainer: {&#xA;    flexDirection: &#x27;row&#x27;,&#xA;    alignItems: &#x27;center&#x27;,&#xA;    marginRight: 10,&#xA;  },&#xA;  clearIcon: {&#xA;    marginRight: 5,&#xA;  },&#xA;  clearAllText: {&#xA;    fontSize: 16,&#xA;    color: &#x27;red&#x27;,&#xA;    textDecorationLine: &#x27;underline&#x27;,&#xA;  },&#xA;  toggleLayoutButton: {&#xA;    backgroundColor: &#x27;#007BFF&#x27;,&#xA;    padding: 1,&#xA;    borderRadius: 8,&#xA;    alignItems: &#x27;center&#x27;,&#xA;    justifyContent: &#x27;center&#x27;,&#xA;  },&#xA;  list: {&#xA;    flex: 1,&#xA;    width: &#x27;100%&#x27;,&#xA;  },&#xA;  videoItem: {&#xA;    padding: 5,&#xA;    borderBottomColor: &#x27;#ccc&#x27;,&#xA;    borderBottomWidth: 0.7,&#xA;    flexDirection: &#x27;row&#x27;,&#xA;    alignItems: &#x27;center&#x27;,&#xA;  },&#xA;  videoItemGrid: {&#xA;    flexDirection: &#x27;column&#x27;,&#xA;    alignItems: &#x27;center&#x27;,&#xA;    margin: 4,&#xA;    borderWidth: 1,&#xA;    borderColor: &#x27;#ccc&#x27;,&#xA;    borderRadius: 8,&#xA;    padding: 2,&#xA;    position: &#x27;relative&#x27;,&#xA;  },&#xA;  thumbnail: {&#xA;    width: 70,&#xA;    height: 70,&#xA;    marginRight: 10,&#xA;  },&#xA;  thumbnailGrid: {&#xA;    width: 80,&#xA;    height: 80,&#xA;    marginBottom: 0,&#xA;  },&#xA;  fileName: {&#xA;    fontSize: 16,&#xA;    marginLeft: 10,&#xA;    flex: 1,&#xA;  },&#xA;  deleteButton: {&#xA;    marginLeft: 60,&#xA;    width: 20,&#xA;    height: 20,&#xA;  },&#xA;  deleteButtonGrid: {&#xA;    position: &#x27;absolute&#x27;,&#xA;    bottom: 5,&#xA;    right: 5,&#xA;  },&#xA;  convertButton: {&#xA;    backgroundColor: &#x27;#007BFF&#x27;,&#xA;    padding: 15,&#xA;    borderRadius: 10,&#xA;    alignItems: &#x27;center&#x27;,&#xA;    marginTop: 20,&#xA;    width: &#x27;100%&#x27;,&#xA;  },&#xA;  convertButtonText: {&#xA;    color: &#x27;#fff&#x27;,&#xA;    fontSize: 18,&#xA;    fontWeight: &#x27;bold&#x27;,&#xA;  },&#xA;  gridContainer: {&#xA;    flexDirection: &#x27;row&#x27;,&#xA;    flexWrap: &#x27;wrap&#x27;,&#xA;    justifyContent: &#x27;flex-start&#x27;,&#xA;    paddingVertical: 5,&#xA;    paddingHorizontal: 5,&#xA;    width: &#x27;100%&#x27;,&#xA;  },&#xA;});

    &#xD;&#xA;

    &#xD;&#xA;

    &#xD;&#xA;&#xA;

    App.json

    &#xA;

    &#xD;&#xA;
    &#xD;&#xA;
    {&#xA;  "expo": {&#xA;    "name": "VidoeConvert",&#xA;    "slug": "VidoeConvert",&#xA;    "version": "1.0.0",&#xA;    "orientation": "portrait",&#xA;    "icon": "./assets/icon.png",&#xA;    "userInterfaceStyle": "light",&#xA;    "splash": {&#xA;      "image": "./assets/splash.png",&#xA;      "resizeMode": "contain",&#xA;      "backgroundColor": "#ffffff"&#xA;    },&#xA;    "ios": {&#xA;      "supportsTablet": true&#xA;    },&#xA;    "android": {&#xA;      "adaptiveIcon": {&#xA;        "foregroundImage": "./assets/adaptive-icon.png",&#xA;        "backgroundColor": "#ffffff"&#xA;      },&#xA;      "package": "com.anonymous.VidoeConvert"&#xA;    },&#xA;    "web": {&#xA;      "favicon": "./assets/favicon.png"&#xA;    },&#xA;    "plugins": [&#xA;      "@config-plugins/ffmpeg-kit-react-native",&#xA;      "expo-build-properties"&#xA;    ]&#xA;  }&#xA;}

    &#xD;&#xA;

    &#xD;&#xA;

    &#xD;&#xA;&#xA;

    Package.json

    &#xA;

    &#xD;&#xA;
    &#xD;&#xA;
    {&#xA;  "name": "vidoeconvert",&#xA;  "version": "1.0.0",&#xA;  "main": "expo/AppEntry.js",&#xA;  "scripts": {&#xA;    "start": "expo start",&#xA;    "android": "expo run:android",&#xA;    "ios": "expo run:ios",&#xA;    "web": "expo start --web"&#xA;  },&#xA;  "dependencies": {&#xA;    "@config-plugins/ffmpeg-kit-react-native": "^8.0.0",&#xA;    "@expo/metro-runtime": "~3.2.1",&#xA;    "expo": "~51.0.17",&#xA;    "expo-asset": "~10.0.10",&#xA;    "expo-av": "^14.0.6",&#xA;    "expo-document-picker": "~12.0.2",&#xA;    "expo-file-system": "~17.0.1",&#xA;    "expo-image-picker": "~15.0.7",&#xA;    "expo-media-library": "~16.0.4",&#xA;    "expo-status-bar": "~1.12.1",&#xA;    "ffmpeg-kit-react-native": "^6.0.2",&#xA;    "react": "18.2.0",&#xA;    "react-dom": "18.2.0",&#xA;    "react-native": "^0.74.3",&#xA;    "react-native-document-picker": "^9.3.0",&#xA;    "react-native-ffmpeg": "^0.5.2",&#xA;    "react-native-vector-icons": "^10.1.0",&#xA;    "react-native-web": "~0.19.10",&#xA;    "expo-build-properties": "~0.12.3"&#xA;  },&#xA;  "devDependencies": {&#xA;    "@babel/core": "^7.20.0"&#xA;  },&#xA;  "private": true&#xA;}

    &#xD;&#xA;

    &#xD;&#xA;

    &#xD;&#xA;&#xA;

    Has anyone encountered a similar issue or can point me in the right direction to resolve this error ? Any help would be greatly appreciated !

    &#xA;

    How to remove the error ?

    &#xA;

    any configruation required for this project ?

    &#xA;

  • How to Record Video of a Dynamic Div Containing Multiple Media Elements in React Konva ?

    14 septembre 2024, par Humayoun Saeed

    I'm working on a React application where I need to record a video of a specific div with the class name "layout." This div contains multiple media elements (such as images and videos) that are dynamically rendered inside divisions. I've tried several approaches, including using MediaRecorder, canvas-based recording with html2canvas, RecordRTC, and even ffmpeg, but none seem to capture the entire div along with its dynamic content effectively.

    &#xA;

    What would be the best approach to achieve this ? How can I record a video of this dynamically rendered div including all its media elements, ensuring a smooth capture of the transitions ?

    &#xA;

    What I’ve Tried :&#xA;MediaRecorder API : Didn't work effectively for capturing the entire div and its elements.&#xA;html2canvas : Captures snapshots but struggles with smooth transitions between media elements.&#xA;RecordRTC HTML Element Recording : Attempts to capture the canvas, but the output video size is 0 bytes.&#xA;CanvasRecorder, FFmpeg, and various other libraries also didn't provide the desired result.

    &#xA;

    import React, { useEffect, useState, useRef } from "react";&#xA;&#xA;const Preview = ({ layout, onClose }) => {&#xA;  const [currentContent, setCurrentContent] = useState([]);&#xA;  const totalDuration = useRef(0);&#xA;  const videoRefs = useRef([]); // Store refs to each video element&#xA;  const [totalTime, setTotalTime] = useState(0); // Add this line&#xA;  const [elapsedTime, setElapsedTime] = useState(0); // Track elapsed time in seconds&#xA;&#xA;  // video recording variable and state declaration&#xA;  //  video recorder end&#xA;  // for video record useffect&#xA;  // Function to capture the renderDivision content&#xA;&#xA;  const handleDownload = async () => {&#xA;    console.log("video downlaod function in developing mode.");&#xA;  };&#xA;&#xA;  // end video record useffect&#xA;&#xA;  // to apply motion and swtich in media of division start&#xA;  useEffect(() => {&#xA;    if (layout &amp;&amp; layout.divisions) {&#xA;      const content = layout.divisions.map((division) => {&#xA;        let divisionDuration = 0;&#xA;&#xA;        division.imageSrcs.forEach((src, index) => {&#xA;          const mediaDuration = division.durations[index]&#xA;            ? division.durations[index] * 1000 // Convert to milliseconds&#xA;            : 5000; // Fallback to 5 seconds if duration is missing&#xA;          divisionDuration &#x2B;= mediaDuration;&#xA;        });&#xA;&#xA;        return {&#xA;          division,&#xA;          contentIndex: 0,&#xA;          divisionDuration,&#xA;        };&#xA;      });&#xA;&#xA;      // Find the maximum duration&#xA;      const maxDuration = Math.max(...content.map((c) => c.divisionDuration));&#xA;&#xA;      // Filter divisions that have the max duration&#xA;      const maxDurationDivisions = content.filter(&#xA;        (c) => c.divisionDuration === maxDuration&#xA;      );&#xA;&#xA;      // Select the first one if there are multiple with the same max duration&#xA;      const selectedMaxDurationDivision = maxDurationDivisions[0];&#xA;&#xA;      totalDuration.current = selectedMaxDurationDivision.divisionDuration; // Update the total duration in milliseconds&#xA;&#xA;      setTotalTime(Math.floor(totalDuration.current / 1000000)); // Convert to seconds and set in state&#xA;&#xA;      // console.log(&#xA;      //   "Division with max duration (including ties):",&#xA;      //   selectedMaxDurationDivision&#xA;      // );&#xA;&#xA;      setCurrentContent(content);&#xA;    }&#xA;  }, [layout]);&#xA;&#xA;  useEffect(() => {&#xA;    if (currentContent.length > 0) {&#xA;      const timers = currentContent.map(({ division, contentIndex }, i) => {&#xA;        const duration = division.durations[contentIndex]&#xA;          ? division.durations[contentIndex] // Duration is already in ms&#xA;          : 5000; // Default to 5000ms if no duration is defined&#xA;&#xA;        const mediaElement = videoRefs.current[i];&#xA;        if (mediaElement &amp;&amp; mediaElement.pause) {&#xA;          mediaElement.pause();&#xA;        }&#xA;&#xA;        // Set up a timeout for each division to move to the next media after duration&#xA;        const timeoutId = setTimeout(() => {&#xA;          // Update content for each division independently&#xA;          updateContent(i, division, contentIndex, duration); // Move to the next content after duration&#xA;&#xA;          // Ensure proper cleanup&#xA;          if (contentIndex &#x2B; 1 >= division.imageSrcs.length) {&#xA;            clearTimeout(timeoutId); // Clear timeout to stop looping&#xA;          }&#xA;        }, duration);&#xA;&#xA;        // Cleanup timers on component unmount&#xA;        return timeoutId;&#xA;      });&#xA;&#xA;      // Return cleanup function to clear all timeouts&#xA;      return () => timers.forEach((timer) => clearTimeout(timer));&#xA;    }&#xA;  }, [currentContent]);&#xA;  // to apply motion and swtich in media of division end&#xA;&#xA;  // Handle video updates when the duration is changed or a new video starts&#xA;  const updateContent = (i, division, contentIndex, duration) => {&#xA;    const newContent = [...currentContent];&#xA;&#xA;    // Check if we are on the last media item&#xA;    if (contentIndex &#x2B; 1 &lt; division.imageSrcs.length) {&#xA;      // Move to next media if not the last one&#xA;      newContent[i].contentIndex = contentIndex &#x2B; 1;&#xA;    } else {&#xA;      // If this is the last media item, pause here&#xA;      newContent[i].contentIndex = contentIndex; // Keep it at the last item&#xA;      setCurrentContent(newContent);&#xA;&#xA;      // Handle video pause if the last media is a video&#xA;      const mediaElement = videoRefs.current[i];&#xA;      if (mediaElement &amp;&amp; mediaElement.tagName === "VIDEO") {&#xA;        mediaElement.pause();&#xA;        mediaElement.currentTime = mediaElement.duration; // Pause at the end of the video&#xA;      }&#xA;      return; // Exit the function as we don&#x27;t want to loop anymore&#xA;    }&#xA;&#xA;    // Update state to trigger rendering of the next media&#xA;    setCurrentContent(newContent);&#xA;&#xA;    // Handle video playback for the next media item&#xA;    const mediaElement = videoRefs.current[i];&#xA;    if (mediaElement) {&#xA;      mediaElement.pause();&#xA;      mediaElement.currentTime = 0;&#xA;      mediaElement&#xA;        .play()&#xA;        .catch((error) => console.error("Error playing video:", error));&#xA;    }&#xA;  };&#xA;&#xA;  const renderDivision = (division, contentIndex, index) => {&#xA;    const mediaSrc = division.imageSrcs[contentIndex];&#xA;&#xA;    if (!division || !division.imageSrcs || division.imageSrcs.length === 0) {&#xA;      return (&#xA;        &#xA;          <p>No media available</p>&#xA;        &#xA;      );&#xA;    }&#xA;&#xA;    if (!mediaSrc) {&#xA;      return (&#xA;        &#xA;          <p>No media available</p>&#xA;        &#xA;      );&#xA;    }&#xA;&#xA;    if (mediaSrc.endsWith(".mp4")) {&#xA;      return (&#xA;        > (videoRefs.current[index] = el)}&#xA;          src={mediaSrc}&#xA;          autoPlay&#xA;          controls={false}&#xA;          style={{&#xA;            width: "100%",&#xA;            height: "100%",&#xA;            objectFit: "cover",&#xA;            pointerEvents: "none",&#xA;          }}&#xA;          onLoadedData={() => {&#xA;            // Ensure video is properly loaded&#xA;            const mediaElement = videoRefs.current[index];&#xA;            if (mediaElement &amp;&amp; mediaElement.readyState >= 3) {&#xA;              mediaElement.play().catch((error) => {&#xA;                console.error("Error attempting to play the video:", error);&#xA;              });&#xA;            }&#xA;          }}&#xA;        />&#xA;      );&#xA;    } else {&#xA;      return (&#xA;        &#xA;      );&#xA;    }&#xA;  };&#xA;&#xA;  // progress bar code start&#xA;  useEffect(() => {&#xA;    if (totalDuration.current > 0) {&#xA;      // Reset elapsed time at the start&#xA;      setElapsedTime(0);&#xA;&#xA;      const interval = setInterval(() => {&#xA;        setElapsedTime((prevTime) => {&#xA;          // Increment the elapsed time by 1 second if it&#x27;s less than the total time&#xA;          if (prevTime &lt; totalTime) {&#xA;            return prevTime &#x2B; 1;&#xA;          } else {&#xA;            clearInterval(interval); // Clear the interval when totalTime is reached&#xA;            return prevTime;&#xA;          }&#xA;        });&#xA;      }, 1000); // Update every second&#xA;&#xA;      // Clean up the interval on component unmount&#xA;      return () => clearInterval(interval);&#xA;    }&#xA;  }, [totalTime]);&#xA;&#xA;  // progress bar code end&#xA;&#xA;  return (&#xA;    &#xA;      &#xA;        &#xA;          Close&#xA;        &#xA;        <h2>Preview Layout: {layout.name}</h2>&#xA;        &#xA;          {currentContent.map(({ division, contentIndex }, i) => (&#xA;            &#xA;              {renderDivision(division, contentIndex, i)}&#xA;            &#xA;          ))}&#xA;          {/* canvas code for video start */}&#xA;          {/* canvas code for video end */}&#xA;          {/* Progress Bar and Time */}&#xA;          / Background color for progress bar track&#xA;              display: "flex",&#xA;              justifyContent: "space-between",&#xA;              alignItems: "center",&#xA;            }}&#xA;          >&#xA;             totalTime) * 100}%)`,&#xA;                backgroundColor: "#28a745", // Green color for progress bar&#xA;                transition: "width 0.5s linear", // Smooth transition&#xA;              }}&#xA;            >&#xA;&#xA;            {/* Time display */}&#xA;            {/* / Fixed right margin&#xA;                zIndex: 1, // Ensure it&#x27;s above the progress bar&#xA;                padding: "5px",&#xA;                fontSize: "18px",&#xA;                fontWeight: "600",&#xA;                color: "#333",&#xA;                // backgroundColor: "rgba(255, 255, 255, 0.8)", // Add a subtle background for readability&#xA;              }}&#xA;            >&#xA;              {elapsedTime} / {totalTime}s&#xA;             */}&#xA;          &#xA;        &#xA;&#xA;        {/* Download button */}&#xA;        > (e.target.style.backgroundColor = "#218838")}&#xA;          onMouseOut={(e) => (e.target.style.backgroundColor = "#28a745")}&#xA;        >&#xA;          Download Video&#xA;        &#xA;        {/* {recording &amp;&amp; <p>Recording in progress...</p>} */}&#xA;      &#xA;    &#xA;  );&#xA;};&#xA;&#xA;export default Preview;&#xA;&#xA;

    &#xA;

    I tried several methods to record the content of the div with the class "layout," which contains dynamic media elements such as images and videos. The approaches I attempted include :

    &#xA;

    MediaRecorder API : I expected this API to capture the entire div and its contents, but it didn't handle the rendering of all dynamic media elements properly.

    &#xA;

    html2canvas : I used this to capture the layout as a canvas and then attempted to convert it into a video stream. However, it could not capture smooth transitions between media elements, leading to a choppy or incomplete video output.

    &#xA;

    RecordRTC : I integrated RecordRTC to capture the canvas stream of the div. Despite setting up the recorder, the resulting video file either had a 0-byte size or only captured parts of the content inconsistently.

    &#xA;

    FFmpeg and other libraries : I explored these tools hoping they would provide a seamless capture of the dynamic content, but they also failed to capture the full media elements, including videos playing within the layout.

    &#xA;

    In all cases, I expected to get a complete video recording of the div, including all media transitions, but the results were incomplete or not functional.

    &#xA;

    Now, I’m seeking an approach or best practice to record the entire div with its dynamic content and media playback.

    &#xA;

  • Video streamign with FFMpeg and Nest.js+Next.js

    17 septembre 2024, par Aizen

    Here is my problem : I have one video src 1080p (on the frontend). On the frontend, I send this video-route to the backend :

    &#xA;

    const req = async()=>{try{const res = await axios.get(&#x27;/catalog/item&#x27;,{params:{SeriesName:seriesName}});return {data:res.data};}catch(err){console.log(err);return false;}}const fetchedData = await req();-On the backend i return seriesName.Now i can make a full path,what the video is,and where it is,code:&#xA;

    &#xA;

    const videoUrl = &#x27;C:/Users/arMori/Desktop/RedditClone/reddit/public/videos&#x27;;console.log(&#x27;IT VideoURL&#x27;,videoUrl);&#xA;

    &#xA;

    const selectedFile = `${videoUrl}/${fetchedData.data.VideoSource}/${seriesName}-1080p.mp4`&#xA;console.log(`ITS&#x27;S SELECTED FILE: ${selectedFile}`);&#xA;

    &#xA;

    Ok, I have my src 1080p, now is the time to send it to the backend :

    &#xA;

    const response = await axios.post(&#x27;/videoFormat&#x27;, {videoUrl:selectedFile})console.log(&#x27;Это консоль лог путей: &#x27;,response.data);const videoPaths = response.data;&#xA;

    &#xA;

    Backend takes it and FFMpqg makes two types of resolution,720p and 480p,save it to the temp storage on backend, and then returns two routes where these videos stores

    &#xA;

    async videoUpload(videoUrl:string){try{const tempDir = C:/Users/arMori/Desktop/RedditClone/reddit_back/src/video/temp;const inputFile = videoUrl;console.log(&#x27;VIDEOURL: &#x27;,videoUrl);&#xA;

    &#xA;

            const outputFiles = [];&#xA;        &#xA;        await this.createDirectories(tempDir);        &#xA;        outputFiles.push(await this.convertVideo(inputFile, &#x27;1280x720&#x27;, &#x27;720p.mp4&#x27;));&#xA;        outputFiles.push(await this.convertVideo(inputFile, &#x27;854x480&#x27;, &#x27;480p.mp4&#x27;));&#xA;        console.log(&#x27;OUTUPT FILES SERVICE: &#x27;,outputFiles);&#xA;        &#xA;        return outputFiles;&#xA;&#xA;    }catch(err){&#xA;        console.error(&#x27;VideoFormatterService Error: &#x27;,err);&#xA;        &#xA;    }&#xA;}&#xA;&#xA;private convertVideo(inputPath:string,resolution:string,outputFileName:string):Promise<string>{&#xA;    const temp = `C:/Users/arMori/Desktop/RedditClone/reddit_back/src/video/temp`;&#xA;    return new Promise(async(resolve,reject)=>{&#xA;        const height = resolution.split(&#x27;x&#x27;)[1];&#xA;        console.log(&#x27;HIEGHT: &#x27;,height);&#xA;        &#xA;        const outputDir = `C:/Users/arMori/Desktop/RedditClone/reddit_back/src/video/temp/${height}p`;&#xA;        const outputPath = join(outputDir, outputFileName);&#xA;        const isExists = await fs.access(outputPath).then(() => true).catch(() => false);&#xA;        if(isExists){ &#xA;            console.log(`File already exists: ${outputPath}`);&#xA;            return resolve(outputPath)&#xA;        };&#xA;        ffmpeg(inputPath)&#xA;        .size(`${resolution}`)&#xA;        .videoCodec(&#x27;libx264&#x27;) // Кодек H.264&#xA;        .audioCodec(&#x27;aac&#x27;) &#xA;        .output(outputPath)&#xA;        .on(&#x27;end&#x27;,()=>resolve(outputPath))&#xA;        .on(&#x27;error&#x27;,(err)=>reject(err))&#xA;        .run()&#xA;            &#xA;    })&#xA;}&#xA;&#xA;private async createDirectories(temp:string){&#xA;    try{&#xA;        const dir720p = `${temp}/720p`;&#xA;        const dir480p = `${temp}/480p`;&#xA;        const dir720pExists = await fs.access(dir720p).then(() => true).catch(() => false);&#xA;        const dir480pExists = await fs.access(dir480p).then(() => true).catch(() => false);&#xA;        if(dir720pExists &amp;&amp; dir480pExists){&#xA;            console.log(&#x27;FILES ALIVE&#x27;);&#xA;            return;&#xA;        }&#xA;        if (!dir720pExists) {&#xA;            await fs.mkdir(dir720p, { recursive: true });&#xA;            console.log(&#x27;Папка 720p создана&#x27;);&#xA;        }&#xA;        &#xA;        if (!dir480pExists) {&#xA;            await fs.mkdir(dir480p, { recursive: true });&#xA;            console.log(&#x27;Папка 480p создана&#x27;);&#xA;        }&#xA;    } catch (err) {&#xA;        console.error(&#x27;Ошибка при создании директорий:&#x27;, err);&#xA;    }&#xA;}&#xA;</string>

    &#xA;

    Continue frontentd code :

    &#xA;

    let videoPath;&#xA;&#xA;if (quality === &#x27;720p&#x27;) {&#xA;        videoPath = videoPaths[0];&#xA;} else if (quality === &#x27;480p&#x27;) {&#xA;        videoPath = videoPaths[1];&#xA;}&#xA;&#xA;if (!videoPath) {&#xA;        console.error(&#x27;Video path not found!&#x27;);&#xA;        return;&#xA;}&#xA;&#xA;// Получаем видео по его пути&#xA;console.log(&#x27;VIDEOPATH LOG: &#x27;,videoPath);&#xA;    &#xA;const videoRes = await axios.get(&#x27;/videoFormat/getVideo&#x27;, { &#xA;        params: { path: videoPath } ,&#xA;        headers: { Range: &#x27;bytes=0-&#x27; },&#xA;        responseType: &#x27;blob&#x27;&#xA;    });&#xA;    console.log(&#x27;Video fetched: &#x27;, videoRes);&#xA;    const videoBlob = new Blob([videoRes.data], { type: &#x27;video/mp4&#x27; });&#xA;    const videoURL = URL.createObjectURL(videoBlob);&#xA;    return videoURL;&#xA;    /* console.log(&#x27;Видео успешно загружено:&#x27;, response.data); */&#xA;    } catch (error) {&#xA;    console.error(&#x27;Ошибка при загрузке видео:&#x27;, error);&#xA;    }&#xA;}&#xA;

    &#xA;

    Here I just choose one of the route and make a new GET request (VideoRes), now in the controller in the backend, I'm trying to do a video streaming :

    &#xA;

    @Public()&#xA;    @Get(&#x27;/getVideo&#x27;)&#xA;    async getVideo(@Query(&#x27;path&#x27;) videoPath:string,@Req() req:Request,@Res() res:Response){&#xA;        try {&#xA;            console.log(&#x27;PATH ARGUMENT: &#x27;,videoPath);&#xA;            console.log(&#x27;VIDEOPATH IN SERVICE: &#x27;,videoPath);&#xA;        const videoSize = (await fs.stat(videoPath)).size;&#xA;        const CHUNK_SIZE = 10 ** 6;&#xA;        const range = req.headers[&#x27;range&#x27;] as string | undefined;&#xA;        if (!range) {&#xA;            return new ForbiddenException(&#x27;Range не найденно&#x27;);&#xA;        }&#xA;        const start = Number(range.replace(/\D/g,""));&#xA;        const end = Math.min(start &#x2B; CHUNK_SIZE,videoSize - 1);&#xA;&#xA;        const contentLength = end - start &#x2B; 1;&#xA;        const videoStream = fsSync.createReadStream(videoPath, { start, end });&#xA;        const headers = {&#xA;            &#x27;Content-Range&#x27;:`bytes ${start}-${end}/${videoSize}`,&#xA;            &#x27;Accept-Ranges&#x27;:&#x27;bytes&#x27;,&#xA;            &#x27;Content-Length&#x27;:contentLength,&#xA;            &#x27;Content-Type&#x27;:&#x27;video/mp4&#x27;&#xA;        }&#xA;        &#xA;        res.writeHead(206,headers);&#xA;&#xA;        // Передаем поток в ответ&#xA;        videoStream.pipe(res);&#xA;        &#xA;&#xA;        // Если возникнет ошибка при стриминге, логируем ошибку&#xA;        videoStream.on(&#x27;error&#x27;, (error) => {&#xA;            console.error(&#x27;Ошибка при чтении видео:&#x27;, error);&#xA;            res.status(500).send(&#x27;Ошибка при чтении видео&#x27;);&#xA;        });&#xA;        } catch (error) {&#xA;            console.error(&#x27;Ошибка при обработке запросов:&#x27;, error);&#xA;            return res.status(400).json({ message: &#x27;Ошибка при обработке getVideo запросов&#x27; });&#xA;        }&#xA;    }&#xA;

    &#xA;

    Send to the frontend

    &#xA;

    res.writeHead(206,headers);&#xA;

    &#xA;

    In the frontend, I make blob url for video src and return it

    &#xA;

    const videoBlob = new Blob([videoRes.data], { type: &#x27;video/mp4&#x27; });const videoURL = URL.createObjectURL(videoBlob);return videoURL;&#xA;

    &#xA;

    And assign src to the video :

    &#xA;

    useVideo(seriesName,quality).then(src => {&#xA;                if (src) {&#xA;                    console.log(&#x27;ITS VIDEOLOGISC GOIDA!&#x27;);&#xA;                    if(!playRef.current) return;&#xA;                    &#xA;                    const oldURL = playRef.current.src;&#xA;                    if (oldURL &amp;&amp; oldURL.startsWith(&#x27;blob:&#x27;)) {&#xA;                        URL.revokeObjectURL(oldURL);&#xA;                    }&#xA;                    playRef.current.pause();&#xA;                    playRef.current.src = &#x27;&#x27;;&#xA;                    setQuality(quality);&#xA;                    console.log(&#x27;SRC: &#x27;,src);&#xA;                    &#xA;                    playRef.current.src = src;&#xA;                    playRef.current.load();&#xA;                    console.log(&#x27;ITS VIDEOURL GOIDA!&#x27;);&#xA;                    togglePlayPause();&#xA;                }&#xA;            })&#xA;            .catch(err => console.error(&#x27;Failed to fetch video&#x27;, err));&#xA;

    &#xA;

    But the problem is :

    &#xA;

    &#xA;

    Vinland-Saga:1 Uncaught (in promise) NotSupportedError : Failed to load because no supported source was found

    &#xA;

    &#xA;

    And I don't know why...

    &#xA;

    I tried everything, but I don't understand why src is incorrect..

    &#xA;