Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/changelog-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential
- uses: actions/checkout@v2
with:
ref: develop
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/check-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential
- name: Install && Build - SDK and Sample App
uses: ./.github/actions/install-and-build-sdk
- name: Lint
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org'
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential

- name: Prepare git
run: |
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/sample-distribution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
- uses: actions/checkout@v2
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
Expand Down
12 changes: 12 additions & 0 deletions examples/SampleApp/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: "com.facebook.react"

// Some libraries look up the React Native location on the application project
// instead of the root project. Mirror the root setting here so they never need
// to shell out to a bare `node` binary just to resolve react-native.
ext.REACT_NATIVE_NODE_MODULES_DIR =
rootProject.ext.has("REACT_NATIVE_NODE_MODULES_DIR")
? rootProject.ext.get("REACT_NATIVE_NODE_MODULES_DIR")
: file("$rootDir/../node_modules/react-native").absolutePath
ext.REACT_NATIVE_WORKLETS_NODE_MODULES_DIR =
rootProject.ext.has("REACT_NATIVE_WORKLETS_NODE_MODULES_DIR")
? rootProject.ext.get("REACT_NATIVE_WORKLETS_NODE_MODULES_DIR")
: file("$rootDir/../node_modules/react-native-worklets").absolutePath

/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
Expand Down
26 changes: 17 additions & 9 deletions examples/SampleApp/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@ buildscript {
}
}

ext.REACT_NATIVE_NODE_MODULES_DIR = file("$rootDir/../node_modules/react-native").absolutePath
ext.REACT_NATIVE_WORKLETS_NODE_MODULES_DIR =
file("$rootDir/../node_modules/react-native-worklets").absolutePath

subprojects { subproject ->
if (subproject.path != ":app") {
evaluationDependsOn(":app")
project(":app").tasks.matching { task ->
task.name.startsWith("configureCMake")
}.configureEach {
dependsOn(subproject.tasks.matching { it.name == "preBuild" })
}
}
}

allprojects {
repositories {
maven {
Expand All @@ -39,15 +54,8 @@ allprojects {
}

project(':app') {
afterEvaluate {
if (tasks.findByName("preBuild")) {
tasks.preBuild.doFirst {
exec {
workingDir rootDir
commandLine './gradlew', 'generateCodegenArtifactsFromSchema'
}
}
}
tasks.matching { it.name == "preBuild" || it.name.startsWith("configureCMake") }.configureEach {
dependsOn("generateCodegenArtifactsFromSchema")
}
}

Expand Down
2 changes: 1 addition & 1 deletion examples/SampleApp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-camera-roll/camera-roll": "^7.10.0",
"@react-native-clipboard/clipboard": "^1.16.3",
"@react-native-community/blur": "^4.4.1",
"@react-native-community/geolocation": "^3.4.0",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-community/blur": "^4.4.1",
"@react-native-documents/picker": "^10.1.3",
"@react-native-firebase/app": "22.2.1",
"@react-native-firebase/messaging": "22.2.1",
Expand Down
6 changes: 3 additions & 3 deletions examples/SampleApp/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2190,9 +2190,9 @@
merge-options "^3.0.4"

"@react-native-camera-roll/camera-roll@^7.10.0":
version "7.10.0"
resolved "https://registry.yarnpkg.com/@react-native-camera-roll/camera-roll/-/camera-roll-7.10.0.tgz#5e9518d78a9cd87ddc8e68d03e31a608df5033ab"
integrity sha512-Zm1yHxxTQS2APsnnxUFoLnK+DMMTPqmIQ2z2pGtNyHRXAG40Nt4MLVB3tDJTWnuJLAG87BpTCEvpz49+u0YkUw==
version "7.10.2"
resolved "https://registry.yarnpkg.com/@react-native-camera-roll/camera-roll/-/camera-roll-7.10.2.tgz#af2234f60f0b55aff9afb60888ce7f0669d52593"
integrity sha512-XgJQJDFUycmqSX+MH7vTcRigQwEIQNLIu1GvOngCZRwlSV2mF61UzeruSmmHwkBcGnHZFXkKg9fil0FQVfyglw==

"@react-native-clipboard/clipboard@^1.16.3":
version "1.16.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,34 @@
import java.util.Map;

public class StreamChatExpoPackage extends TurboReactPackage {
private static final String STREAM_VIDEO_THUMBNAIL_MODULE = "StreamVideoThumbnail";

@Nullable
@Override
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
if (name.equals(STREAM_VIDEO_THUMBNAIL_MODULE) && BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
return createNewArchModule("com.streamchatexpo.StreamVideoThumbnailModule", reactContext);
}

return null;
}

@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
return () -> {
final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
moduleInfos.put(
STREAM_VIDEO_THUMBNAIL_MODULE,
new ReactModuleInfo(
STREAM_VIDEO_THUMBNAIL_MODULE,
STREAM_VIDEO_THUMBNAIL_MODULE,
false, // canOverrideExistingModule
false, // needsEagerInit
false, // hasConstants
false, // isCxxModule
isTurboModule // isTurboModule
));
return moduleInfos;
};
}
Expand All @@ -32,4 +50,19 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.<ViewManager>singletonList(new StreamShimmerViewManager());
}

@Nullable
private NativeModule createNewArchModule(
String className,
ReactApplicationContext reactContext
) {
try {
Class<?> moduleClass = Class.forName(className);
return (NativeModule) moduleClass
.getConstructor(ReactApplicationContext.class)
.newInstance(reactContext);
} catch (Throwable ignored) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.streamchatexpo

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.streamchatreactnative.shared.StreamVideoThumbnailGenerator
import java.util.concurrent.Executors

class StreamVideoThumbnailModule(
reactContext: ReactApplicationContext,
) : NativeStreamVideoThumbnailSpec(reactContext) {
override fun getName(): String = NAME

override fun createVideoThumbnails(urls: ReadableArray, promise: Promise) {
val urlList = mutableListOf<String>()
for (index in 0 until urls.size()) {
urlList.add(urls.getString(index) ?: "")
}

executor.execute {
try {
val thumbnails = StreamVideoThumbnailGenerator.generateThumbnails(reactApplicationContext, urlList)
val result = Arguments.createArray()
thumbnails.forEach { thumbnail ->
val thumbnailMap = Arguments.createMap()
if (thumbnail.uri != null) {
thumbnailMap.putString("uri", thumbnail.uri)
} else {
thumbnailMap.putNull("uri")
}
if (thumbnail.error != null) {
thumbnailMap.putString("error", thumbnail.error)
} else {
thumbnailMap.putNull("error")
}
result.pushMap(thumbnailMap)
}
promise.resolve(result)
} catch (error: Throwable) {
promise.reject("stream_video_thumbnail_error", error.message, error)
}
}
}

companion object {
const val NAME = "StreamVideoThumbnail"
private val executor = Executors.newCachedThreadPool()
}
}
6 changes: 6 additions & 0 deletions package/expo-package/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@
"expo-image-picker": {
"optional": true
},
"expo-image-manipulator": {
"optional": true
},
"expo-sharing": {
"optional": true
},
Expand All @@ -91,6 +94,9 @@
"type": "all",
"jsSrcsDir": "src/native",
"ios": {
"modulesProvider": {
"StreamVideoThumbnail": "StreamVideoThumbnail"
},
"componentProvider": {
"StreamShimmerView": "StreamShimmerViewComponentView"
}
Expand Down
14 changes: 14 additions & 0 deletions package/expo-package/src/native/NativeStreamVideoThumbnail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { TurboModule } from 'react-native';

import { TurboModuleRegistry } from 'react-native';

export type VideoThumbnailResult = {
error?: string | null;
uri?: string | null;
};

export interface Spec extends TurboModule {
createVideoThumbnails(urls: ReadonlyArray<string>): Promise<ReadonlyArray<VideoThumbnailResult>>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('StreamVideoThumbnail');
8 changes: 8 additions & 0 deletions package/expo-package/src/native/videoThumbnail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import NativeStreamVideoThumbnail, { type VideoThumbnailResult } from './NativeStreamVideoThumbnail';

export type { VideoThumbnailResult } from './NativeStreamVideoThumbnail';

export const createVideoThumbnails = async (urls: string[]): Promise<VideoThumbnailResult[]> => {
const results = await NativeStreamVideoThumbnail.createVideoThumbnails(urls);
return Array.from(results);
};
8 changes: 3 additions & 5 deletions package/expo-package/src/optionalDependencies/AudioVideo.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
let AudioComponent;
let VideoComponent;
let RecordingObject;

try {
const audioVideoPackage = require('expo-av');
AudioComponent = audioVideoPackage.Audio;
VideoComponent = audioVideoPackage.Video;
RecordingObject = audioVideoPackage.RecordingObject;
} catch (e) {
// do nothing
}

if (!AudioComponent || !VideoComponent) {
if (!AudioComponent) {
console.log(
'Audio Video library is currently not installed. To allow in-app audio playback, install the "expo-av" package.',
'The audio library is currently not installed. To allow in-app audio playback, install the "expo-av" package.',
);
}

export { AudioComponent, RecordingObject, VideoComponent };
export { AudioComponent, RecordingObject };
34 changes: 3 additions & 31 deletions package/expo-package/src/optionalDependencies/Video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect } from 'react';

import { useEventListener } from 'expo';

import { AudioComponent, VideoComponent as ExpoAVVideoComponent } from './AudioVideo';
import { AudioComponent } from './AudioVideo';

let videoPackage;

Expand All @@ -18,8 +18,8 @@ if (!videoPackage) {
);
}

const VideoComponent = videoPackage ? videoPackage.VideoView : ExpoAVVideoComponent;
const useVideoPlayer = videoPackage ? videoPackage.useVideoPlayer : null;
const VideoComponent = videoPackage?.VideoView;
const useVideoPlayer = videoPackage?.useVideoPlayer;

let Video = null;

Expand Down Expand Up @@ -84,33 +84,5 @@ if (videoPackage) {
);
};
}
// expo-av
else if (ExpoAVVideoComponent) {
Video = ({ onPlaybackStatusUpdate, paused, resizeMode, style, uri, videoRef, rate }) => {
// This is done so that the audio of the video is not muted when the phone is in silent mode for iOS.
useEffect(() => {
const initializeSound = async () => {
await AudioComponent.setAudioModeAsync({
playsInSilentModeIOS: true,
});
};
initializeSound();
}, []);

return (
<VideoComponent
onPlaybackStatusUpdate={onPlaybackStatusUpdate}
ref={videoRef}
resizeMode={resizeMode}
shouldPlay={!paused}
source={{
uri,
}}
style={[style]}
playbackRate={rate}
/>
);
};
}

export { Video };
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createGenerateVideoThumbnails } from 'stream-chat-react-native-core/src/utils/createGenerateVideoThumbnails';

import { createVideoThumbnails, type VideoThumbnailResult } from '../native/videoThumbnail';

export const generateThumbnails: (
uris: string[],
) => Promise<Record<string, VideoThumbnailResult>> = createGenerateVideoThumbnails({
createVideoThumbnails,
});
Loading
Loading