Потоковое видео mp4 (mongodbgridfsbucket) в приложении nextJS

Я пытаюсь передать видеофайл в формате mp4 в своем приложении nextJS. Файл хранится в GridFSBucket mongodb.

Итак, для этого я использую getServerSideProps(). При вызове страницы в браузере Chrome отображается видеоплеер, но видео не воспроизводится (продолжительность 0:00). Я вижу несколько сообщений журнала warn - You should not access 'res' after getServerSideProps resolves.. Но я не думаю, что это проблема, поскольку это всего лишь предупреждение.

Возможно, кто-то заметит фундаментальную ошибку в моем коде, поскольку я не знаю, как отладить проблему без каких-либо сообщений об ошибках.

[хеш].tsx

export const getServerSideProps: GetServerSideProps = async ({
    res,
    query: { hash }
}) => {        
    const database = await mongodb()
    const Videos = database.collection('videos')

    const { fileId } = await Videos.findOne({ uid: hash })

    const Files = new GridFSBucket(database)
    const id = new ObjectId(fileId)

    const file: GridFSFile = await new Promise((resolve, reject) => {
        Files.find({
            _id: id
        }).toArray((err, files) => {
            if (err) reject(err)
            resolve(files[0])
        })
    })
    const { contentType } = file || {}

    res.writeHead('Content-Type', contentType) // contentType is "video/mp4"
    Files.openDownloadStream(id)
    .on('data', (chunk) => {
        res.write(chunk)
    })
    .on('end', () => {
        res.end()
    })
    .on('error', (err) => {
        throw err
    })

    return {
        props: {}
    }
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
0
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете увидеть в разделе «Как интегрировать MongoDB в ваше приложение Next.js / Пример 2: Страницы Next.js с MongoDB » (из Адо Кукича и Кушагра Кесав ) пример использования функция getServerSideProps.
Это хорошо работает для данных, которые вы хотите отобразить на стороне сервера, а затем отправить клиенту в формате HTML.

Однако ваш первоначальный вопрос касается потоковой передачи мультимедийных файлов (в частности, видео MP4), хранящихся в MongoDB GridFS, что представляет собой другой вариант использования, чем тот, который рассматривается в руководстве MongoDB.

  • вы пытаетесь передать двоичные данные (видеофайл), а не запрашивать и отображать текстовые данные (например, названия фильмов и краткое изложение сюжета)
  • вы манипулируете видеофайлами, которые часто имеют большой размер и должны транслироваться в потоковом режиме для поддержки поиска, буферизации и других элементов управления мультимедиа. Не текстовые данные, которые обычно невелики и могут быть легко переданы в качестве реквизита из getServerSideProps().

При работе с потоковыми медиафайлами вам также необходимо правильно настроить HTTP-заголовки, такие как Content-Type, и, возможно, поддерживать запросы диапазона HTTP. Это не входит в типичный вариант использования getServerSideProps().


Если вы хотите использовать видеофайл из MongoDB GridFS, вам следует сделать это, настроив для этого выделенную конечную точку API.

Создайте новый маршрут API, например pages/api/videos/[hash].ts:

import { MongoClient, ObjectId, GridFSBucket } from 'mongodb';

export default async function handler(req, res) {
  if (req.method !== 'GET') {
    res.status(405).end(); //Method Not Allowed
    return;
  }

  const hash = req.query.hash;

  try {
    const database = await mongodb();  // Replace with your mongodb connection logic
    const Videos = database.collection('videos');

    const { fileId } = await Videos.findOne({ uid: hash });

    const Files = new GridFSBucket(database);
    const id = new ObjectId(fileId);

    const file = await new Promise((resolve, reject) => {
      Files.find({ _id: id }).toArray((err, files) => {
        if (err) reject(err);
        resolve(files[0]);
      });
    });
    
    const { contentType } = file || {};

    res.writeHead(200, { 'Content-Type': contentType });

    Files.openDownloadStream(id)
      .on('data', (chunk) => {
        res.write(chunk);
      })
      .on('end', () => {
        res.end();
      })
      .on('error', (err) => {
        res.status(500).json({ error: err.message });
      });

  } catch (error) {
    res.status(500).json({ error: 'Internal Server Error' });
  }
}

Затем в приложении Next.js вы можете использовать тег HTML5 <video> для встраивания видео:

<video controls width = "250">
  <source src = "/api/videos/your-hash-goes-here" type = "video/mp4">
  Your browser does not support the video tag.
</video>

Следуя этому подходу, вы позволяете браузеру обрабатывать потоковое видео и изолируете логику обслуживания мультимедиа от логики получения данных вашей страницы, что лучше подходит для Next.js.


Я вижу плеер, который не может открыть поток. Таймер установлен на 0:00. Разве http://localhost:3002/api/videos/RUaYN не следует уже транслировать файл? Я также добавил немного console.info() в [hash].tsx и вижу, что данные файла считываются правильно (возвращает тип контента видео/mp4), возвращает фрагменты и что-то возвращает в конце. Вот почему я предполагаю, что соединение MongoDB и логика [hash].tsx верны...

Если ваш маршрут API читает файл из MongoDB GridFS, передает фрагменты в потоковом режиме и достигает конца без ошибок, но видеоплеер по-прежнему не работает, то проблема может быть на уровне HTTP или клиента. Поэтому убедитесь, что заголовки HTTP-ответа установлены правильно. В частности, убедитесь, что для Content-Type установлено значение video/mp4. Если вы используете другой тип MIME для своего видео, измените заголовок и тег <source> соответствующим образом.

И проверьте консоль браузера на наличие ошибок или предупреждений. В частности, используйте вкладку «Сеть» инструментов разработчика вашего браузера для проверки HTTP-запроса и ответа. Проверьте код состояния, заголовки и размер содержимого. Убедитесь, что запрос действительно достигает вашей конечной точки /api/videos/[hash].
Убедитесь, что видео имеет правильный формат (MP4) и кодек, понятный видеоплееру HTML5. Несоответствие здесь может привести к сбою воспроизведения, даже если логика на стороне сервера правильна.

В свой серверный код добавьте обработку ошибок и ведение журнала, чтобы перехватывать любые исключения или ошибки, которые могут возникнуть. Это даст вам больше информации о том, что может пойти не так. В настоящее время вы выдаете ошибку в событии ошибки потока, но не обрабатываете ее.

На стороне клиента добавьте прослушиватели событий для error, loadeddata, canplay и т. д. в элемент <video>, чтобы отслеживать его состояние и выявлять потенциальные ошибки. Например:

<video controls width = "600"
    onError = {(e) => {
        console.error("Video error:", e);
    }}
    onLoadedData = {() => {
        console.info("Video data loaded");
    }}>
    <source src = {`/api/videos/${videoHash}`} type = "video/mp4" />
    Your browser does not support the video tag.
</video>

Это дало мне подсказку... Я попробовал еще один пример файла mp4 - и он сработал. Значит, проблема должна быть в самом видеофайле. Когда я смотрю на свойства файла: рабочий файл имеет кодеки MPEG-4 AAC, H.264, мой файл имеет MPEG-4 Video.

Хорошо, проблема, похоже, кроется в видеокодеках. Кодеки — это алгоритмы, используемые для кодирования и декодирования (или сжатия и распаковки) потоков цифровых данных. Что касается видеофайлов, не все кодеки поддерживаются универсально во всех браузерах и медиаплеерах.

Кодеки H.264 (часто упакованные в файлы MP4) и AAC широко поддерживаются и являются стандартными для воспроизведения видео HTML5. С другой стороны, MPEG-4 Видео может не поддерживаться повсеместно, что может привести к проблеме, с которой вы столкнулись.

Возможно, вам придется перекодировать: используйте инструмент преобразования видео, чтобы перекодировать видео с помощью видеокодека H.264 и аудиокодека AAC. Такие инструменты, как FFmpeg, могут быть весьма полезны для этой цели.

ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict experimental output.mp4

Вы также можете использовать инструменты медиаинформации, такие как ffprobe, чтобы проверить детали вашего видеофайла. Это даст вам представление о том, какие кодеки используются, о размерах видео и т. д. Вы можете сравнить это с рабочим файлом, чтобы понять различия.


Можно ли прочитать файл из корзины (потока) MongoDB, перекодировать его с помощью ffmpeg и сохранить обратно в базу данных в Node.js?

Потому что хотелось бы в самом приложении создать функцию перекодирования сбойных видеофайлов.

Должно быть возможно прочитать видеофайл из GridFS MongoDB, перекодировать его с помощью FFmpeg, а затем сохранить обратно в GridFS, и все это в приложении Node.js.

Обычно вы используете пакет gridfs-stream для MongoDB и пакет fluent-ffmpeg для FFmpeg в Node.js.

const mongodb = require('mongodb');
const { GridFSBucket } = require('mongodb');
const ffmpeg = require('fluent-ffmpeg');

// Establish MongoDB connection
mongodb.MongoClient.connect('mongodb://localhost:27017/testDB', async function(err, client) {
  if (err) {
    console.error(err);
    return;
  }

  const db = client.db('testDB');

  // Initialize GridFS
  const bucket = new GridFSBucket(db, {
    bucketName: 'videos'
  });

  const fileId = new mongodb.ObjectId('your-object-id-here'); // Replace with your object ID
  const fileName =  are-encoded-video.mp4'; // New name for the re-encoded file

  // Read the existing video stream from GridFS
  const readStream = bucket.openDownloadStream(fileId);

  // Create write stream to GridFS for the re-encoded video
  const uploadStream = bucket.openUploadStream(fileName);
  const newFileId = uploadStream.id;

  // Set up FFmpeg to read from the GridFS readStream
  ffmpeg(readStream)
    .outputFormat('mp4') // You can specify the output format
    .videoCodec('libx264')
    .audioCodec('aac')
    .on('end', () => {
      console.info( are-encoding succeeded');
      // Optionally, delete the old file from GridFS
      bucket.delete(fileId, (err) => {
        if (err) {
          console.error('Error deleting old file:', err);
        } else {
          console.info('Old file deleted');
        }
      });
    })
    .on('error', (err) => {
      console.error( are-encoding failed:', err);
    })
    .pipe(uploadStream); // Pipe the re-encoded video back to GridFS
});

Заменить:

  • 'your-object-id-here' с идентификатором объекта видео, которое вы хотите перекодировать.
  • are-encoded-video.mp4' с любым новым именем, которое вы хотите дать перекодированному файлу.
  • 'videos' с именем вашего контейнера GridFS.
  • 'testDB' с именем вашей базы данных MongoDB.

В этом примере есть базовая обработка ошибок. В производственном сценарии вам, вероятно, захочется добавить более надежную обработку ошибок. Вам потребуется установить FFmpeg на компьютере, где будет выполняться этот код.

Включив эту функцию в свое приложение, вы можете программно перекодировать видео, которые не воспроизводятся из-за проблем с кодеком.


К сожалению, я получаю сообщение об ошибке: Error: ffmpeg exited with code 1: Conversion failed!. Как узнать, почему это не удалось? Это сообщение об ошибке не очень полезно.

Полученное вами сообщение об ошибке Error: ffmpeg exited with code 1: Conversion failed! действительно является довольно общим и не дает много информации о том, что пошло не так в процессе перекодирования. Чтобы получить дополнительную информацию, вы можете записать поток стандартных ошибок (stderr) из FFmpeg. Пакет fluent-ffmpeg позволяет вам сделать это через свои обработчики событий.

Вот модифицированный фрагмент кода, который фиксирует выходные данные stderr:

ffmpeg(readStream)
  .outputFormat('mp4')
  .videoCodec('libx264')
  .audioCodec('aac')
  .on('stderr', function(stderrLine) {
    console.info('Stderr output: ' + stderrLine);
  })
  .on('end', function() {
    console.info( are-encoding succeeded');
  })
  .on('error', function(err, stdout, stderr) {
    console.error( are-encoding failed:', err);
    console.error('ffmpeg stdout:', stdout);
    console.error('ffmpeg stderr:', stderr);
  })
  .pipe(uploadStream);

Обработчик событий .on('stderr', function(stderrLine) {...}) должен захватывать каждую строку вывода stderr.
Обработчик событий .on('error', function(err, stdout, stderr) {...}) собирает дополнительную информацию, если в процессе перекодирования возникает ошибка.

Эти выходные данные могут предоставить вам дополнительную информацию о том, почему FFmpeg не может перекодировать видео, что может варьироваться от неподдерживаемых кодеков до неправильных настроек FFmpeg.

Просматривая журналы stderr, вы сможете лучше понять, что вызывает сбой преобразования. Имея эту информацию, вы сможете соответствующим образом настроить параметры FFmpeg, чтобы решить проблему.


Я получаю вот такую ​​ошибку: [mp4 @ 0x12a7041d0] muxer does not support non seekable output [out#0/mp4 @ 0x600003bb4000] Could not write header (incorrect codec parameters ?): Invalid argument

У FFmpeg возникают проблемы с выходным форматом или настройками кодека при попытке записи в поток, недоступный для поиска. Потоки GridFS по своей природе не подлежат поиску.

Попробуйте сначала записать перекодированное видео в промежуточный временный файл, а затем загрузить этот файл в GridFS.

ffmpeg(readStream)
  .outputFormat('mp4')
  .videoCodec('libx264')
  .audioCodec('aac')
  .on('end', function() {
    // Read the intermediate file and upload it to GridFS here.
  })
  .on('error', function(err) {
    console.error('Re-encoding failed:', err);
  })
  .save('intermediate-file.mp4');  // Save to a temporary file first

После сохранения файла вы можете прочитать этот промежуточный файл и загрузить его обратно в GridFS. (и удалите промежуточный файл после завершения загрузки).

Спасибо за отличный ответ. К сожалению, это ничего не меняет. Я вижу плеер, который не может открыть поток. Таймер установлен на 0:00.

user3142695 22.09.2023 11:50

Разве http://localhost:3002/api/videos/RUaYN не следует уже транслировать файл? Я также добавил немного console.info() в [hash].tsx и вижу, что данные файла читаются правильно (возвращает тип контента video/mp4), возвращает фрагменты и что-то возвращает в конце. Вот почему я предполагаю, что соединение mongodb и логика [hash].tsx верны...

user3142695 22.09.2023 11:58

@ user3142695 ОК. Я отредактировал ответ, чтобы ответить на ваш комментарий.

VonC 22.09.2023 12:07

Большое спасибо. Это дало мне подсказку... Я попробовал еще один пример файла mp4 - и он сработал. Значит, проблема должна быть в самом видеофайле. Когда я смотрю на свойства файла: у рабочего файла есть кодеки MPEG-4 AAC, H.264, у моего файла есть MPEG-4 Video. Не совсем знаю, как это исправить, но спасибо за вашу огромную помощь...

user3142695 22.09.2023 13:00

@ user3142695 ОК. Я отредактировал ответ, чтобы учесть ваш последний вывод. Хороший улов!

VonC 22.09.2023 13:12

Возможно, у вас есть некоторый опыт в этом: можно ли прочитать файл из ведра (потока) mongodb, перекодировать его с помощью ffmpeg и сохранить обратно в базу данных в nodeJS? Потому что хотелось бы в самом приложении создать функцию перекодирования сбойных видеофайлов..

user3142695 22.09.2023 21:51

@user3142695 user3142695 Я отредактировал ответ, учитывая ваш последний комментарий.

VonC 22.09.2023 22:53

Ты мой герой. К сожалению, я получаю ошибку re-encoding failed: Error: ffmpeg exited with code 1: Conversion failed!. Как узнать, почему это не удалось? Это сообщение об ошибке не очень полезно.

user3142695 23.09.2023 12:25

@user3142695 user3142695 Я отредактировал этот «бесконечный» ответ, чтобы ответить на ваш последний комментарий;)

VonC 23.09.2023 20:14

Извините за этот бесконечный пост... Вот такая ошибка: [mp4 @ 0x12a7041d0] muxer does not support non seekable output [out#0/mp4 @ 0x600003bb4000] Could not write header (incorrect codec parameters ?): Invalid argument

user3142695 24.09.2023 09:08

@ user3142695 user3142695 Я отредактировал ответ, чтобы предложить обходной путь, который может помочь вам избежать этой ошибки.

VonC 24.09.2023 12:24

Другие вопросы по теме