ffmpeg-kit icon indicating copy to clipboard operation
ffmpeg-kit copied to clipboard

Custom-Built ffmpeg-kit-react-native: NativeModule === null - why?

Open vpume opened this issue 7 months ago • 6 comments

Hi, I try to build a custom version of the ffmpeg-kit-react-native package on macOS, for iOS devices as target.

I did the following steps:

  • forked your repo
  • brew install autoconf automake libtool pkg-config curl git doxygen nas
  • ./ios.sh -x
  • ln -s ~/git/ffmpeg-kit/prebuilt/bundle-apple-xcframework-ios ~/git/ffmpeg-kit/bundle-apple-xcframework-ios
  • change ffmpeg-kit-react-native.prodspec to (according to https://github.com/arthenica/ffmpeg-kit/issues/537#issuecomment-1873589426)
require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
  s.name         = package["name"]
  s.version      = package["version"]
  s.summary      = package["description"]
  s.homepage     = package["homepage"]
  s.license      = package["license"]
  s.authors      = package["author"]

  s.platform          = :ios
  s.requires_arc      = true
  s.static_framework  = true

  s.source       = { :path => '/~/git/ffmpeg-kit/bundle-apple-xcframework-ios' }

  # https://github.com/arthenica/ffmpeg-kit/issues/537#issuecomment-1873589426
  s.source_files = 'FFmpegKitReactNativeModule.m', 'FFmpegKitReactNativeModule.h', 'bundle-apple-xcframework-ios/**/*.{h,m,swift}'
  s.vendored_frameworks = 'bundle-apple-xcframework-ios/ffmpegkit.xcframework',
                              'bundle-apple-xcframework-ios/libavcodec.xcframework',
                              'bundle-apple-xcframework-ios/libavdevice.xcframework',
                              'bundle-apple-xcframework-ios/libavfilter.xcframework',
                              'bundle-apple-xcframework-ios/libavformat.xcframework',
                              'bundle-apple-xcframework-ios/libavutil.xcframework',
                              'bundle-apple-xcframework-ios/libswresample.xcframework',
                              'bundle-apple-xcframework-ios/libswscale.xcframework'
  s.ios.deployment_target = '12.1'
  s.dependency "React-Core"
  1. changing package.json of my target app to use my local build: "ffmpeg-kit-react-native": "file:/~/git/ffmpeg-kit/react-native"

building works fine, however, when I run the app, the first time an ffmpeg function is begin used, I get the error Invariant Violation, message: new NativeEventEmitter()requires a non-null argument., stack: Invariant Violation:new NativeEventEmitter() requires a non-null argument.

I tracked down the exact location of the error:

Image

It seems like the FfmpegKitReactNativeModule.m is not being loaded. But if I look in the node_modules folder of the target app, it is all there:

Image

Can anybody give me a hint on what could be the problem? Native Modules are not really my core competence, so I'm a bit lost here.

vpume avatar Apr 06 '25 12:04 vpume

I was running into the same issue. Started to go down another route now. Would love to know the answer to this

cvincentcoleman avatar Apr 06 '25 14:04 cvincentcoleman

@cvincentcoleman which other route is there?

vpume avatar Apr 06 '25 14:04 vpume

@vpume - I'm working on figuring this out now too.

cvincentcoleman avatar Apr 06 '25 21:04 cvincentcoleman

The native library is not linked because the pods are not installing properly.

The root .podspec should have a link to the source.

It doesn't matter where that zip is, it can be anywhere from a vps, google drive, amazon s3 so make sure you create a root .podspec that points to that zip:

here's my root podspec in my private ffmpeg-kit clone, the zip file is a plain zip of the bundle-apple-xcframework-ios folder found inside the prebuilt.

require "json"

Pod::Spec.new do |s|
  s.name         = "APP_NAME-ffmpeg-kit-ios-video" # Match your file name
  s.version      = "6.0.3"
  s.summary      = "FFmpeg Kit iOS Https Shared Framework"
  s.description  = "Includes FFmpeg with gmp and gnutls libraries enabled."
  s.homepage     = "https://github.com/<github name or company>ffmpeg-kit"
  s.license      = { :type => "LGPL-3.0", :text => "Licensed under the LGPL-3.0" }
  s.authors      = "<author_name>"


  s.source = {
    :http => "https://<your server url>/ffmpeg-kit-video-6.0-ios-xcframework.zip"
  }

  s.platform          = :ios
  s.ios.deployment_target = "15.1"
  s.requires_arc      = true
  s.static_framework  = true

  s.libraries = [
    "z",
    "bz2",
    "c++",
    "iconv"
  ]

  s.frameworks = [
    "AudioToolbox",
    "AVFoundation",
    "CoreMedia",
    "VideoToolbox"
  ]

  s.pod_target_xcconfig = {
    'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64'
  }
  s.user_target_xcconfig = {
    'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64'
  }

s.vendored_frameworks = [
  "bundle-apple-xcframework-ios/ffmpegkit.xcframework",
  "bundle-apple-xcframework-ios/libavcodec.xcframework",
  "bundle-apple-xcframework-ios/libavdevice.xcframework",
  "bundle-apple-xcframework-ios/libavfilter.xcframework",
  "bundle-apple-xcframework-ios/libavformat.xcframework",
  "bundle-apple-xcframework-ios/libavutil.xcframework",
  "bundle-apple-xcframework-ios/libswresample.xcframework",
  "bundle-apple-xcframework-ios/libswscale.xcframework",
]

end

create a repo named CocoaPodsSpec on github and make it private.

then on your terminal: pod repo add NAME_IT_WHATEVER [email protected]:<github user name of company>/CocoaPodsSpecs.git

followed by: pod repo push NAME_IT_WHATEVER APP_NAME-ffmpeg-kit-ios-video.podspec

then create another repo with only the react-native folder from ffmpeg-kit repo, put that on the .podspec: (name of the repo is ffmpeg-kit-react-native)

require "json"

package = JSON.parse(File.read(File.join(__dir__, "package.json")))

Pod::Spec.new do |s|
  s.name         = "ffmpeg-kit-react-native"
  s.version      = package["version"]
  s.summary      = package["description"]
  s.homepage     = package["homepage"]
  s.license      = package["license"]
  s.authors      = package["author"]

  s.platform          = :ios
  s.requires_arc      = true
  s.static_framework  = true

  s.source = { :git => "[email protected]:<github username or company>/ffmpeg-kit-react-native.git", :tag => "react.native.v#{s.version}-<your app name>" }

  s.default_subspec   = 'video'

  s.dependency "React-Core"

  s.subspec 'video' do |ss|
      ss.source_files      = '**/FFmpegKitReactNativeModule.m',
                             '**/FFmpegKitReactNativeModule.h'
      ss.dependency 'APP_NAME-ffmpeg-kit-ios-video', "6.0.3"
      ss.ios.deployment_target = '15.1'
  end

end

Create a release and tag it, upload a zip on your ffmpeg-kit-react-native repo that follows the naming convention above:

git tag -a react.native.v6.0.3-<your app name> -m "v6.0.3-<<your app name>>"

then git push

on the app project, yarn add the ffmpeg-kit-react-native from above eg. yarn add ffmpeg-kit-react-native@"ssh://[email protected]/<github username or company>/ffmpeg-kit-react-native"

open the ios/Podfile and at the very top add your private repo holding the CocoaPodsSpec:

source '[email protected]:<github username or company>/CocoaPodsSpecs.git'
source 'https://cdn.cocoapods.org/'

pod install and voila, all set.

ps: you might need to add the github package registry authtoken:

$ nano ~/.npmrc
and add these lines: 
//npm.pkg.github.com/:_authToken=<YOUR_PERSONAL_ACCESS_TOKEN>
github username or company:registry=https://npm.pkg.github.com/

efstathiosntonas avatar Apr 07 '25 18:04 efstathiosntonas

For anyone using expo.

Follow the steps above but at the end instead of updating the ios/Podfile you'll need to create a plugin for updating the Expo Podfile at build.

import path from 'path'
import fs from 'fs'
import {
  withDangerousMod,
  ConfigPlugin,
} from '@expo/config-plugins'

const CUSTOM_SOURCES = `source 'https://cdn.cocoapods.org/'
source '[email protected]:<Your GitHub>/CocoaPodsSpec.git'
`

const customFfmpegPodSource: ConfigPlugin = (config) => {
  return withDangerousMod(config, [
    'ios',
    async (config) => {
      const podfilePath = path.join(
        config.modRequest.projectRoot,
        'ios',
        'Podfile',
      )

      if (fs.existsSync(podfilePath)) {
        let contents = fs.readFileSync(podfilePath, 'utf-8')

        // Only inject if both sources are not already present
        if (
          !contents.includes(
            "source '[email protected]:<Your GitHub>/CocoaPodsSpec.git'",
          )
        ) {
          contents = `${CUSTOM_SOURCES}${contents}`
          fs.writeFileSync(podfilePath, contents)
          console.log('✅ Injected CocoaPods sources into Podfile.')
        } else {
          console.log('ℹ️ CocoaPods sources already present.')
        }
      } else {
        console.warn(`⚠️ Podfile not found at ${podfilePath}`)
      }

      return config
    },
  ])
}

Import this or put it in app.config.ts and then wrap your config with withPlugins like this:

import {
  withPlugins,
} from '@expo/config-plugins'

export default ({ config }: ConfigContext): ExpoConfig =>
  withPlugins({ ...your config object... },  [customFfmpegPodSource])

kierangillen avatar Apr 09 '25 13:04 kierangillen