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
50 changes: 44 additions & 6 deletions src/components/login-modal/profile-form-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { notifyError, notifySuccess } from '@src/libs/notifications/internal-not
import { SUCCESS } from '@src/libs/notifications/success';
import { ERRORS } from '@src/libs/notifications/errors.ts';
import {ProfileFormProps, ProfileFormValues} from "@src/components/login-modal/types.ts"
import { useCreateUserMutation, useUpdateUserMutation } from '@src/graphql/generated/hooks.tsx';
import { useCreateUserMutation, useLogEventMutation, useUpdateUserMutation } from '@src/graphql/generated/hooks.tsx';
import { resolveSrc } from '@src/utils/image.ts';
import { getIpfsUri } from '@src/utils/publication.ts';
import { useAuth } from '@src/hooks/use-auth.ts';
Expand All @@ -51,6 +51,7 @@ export const ProfileFormView: React.FC<ProfileFormProps> = ({
const [registrationLoading, setRegistrationLoading] = useState(false);
const [createUser, { loading: createUserLoading, error: errorCreatingProfile }] = useCreateUserMutation();
const [updateUser, { loading: updateUserLoading, error: errorUpdatingProfile }] = useUpdateUserMutation();
const [logEvent] = useLogEventMutation();
const { session } = useAuth();
const { refreshUser } = useAccountSession();

Expand Down Expand Up @@ -108,6 +109,39 @@ export const ProfileFormView: React.FC<ProfileFormProps> = ({
}
};

const buildPerkEvents = (
data: ProfileData,
profilePictureURI: string | null,
prev?: ProfileData | null,
) => {
const jobs: Promise<unknown>[] = [];

const hadPictureBefore = !!prev?.profilePicture;
if (!hadPictureBefore && profilePictureURI) {
jobs.push(
logEvent({
variables: { input: { type: 'PROFILE_PICTURE_ADDED' } },
}),
);
}

Object.entries(data.socialLinks).forEach(([platform, url]) => {
const prevUrl = prev?.socialLinks?.[platform as keyof typeof data.socialLinks];
if (!prevUrl && url) {
jobs.push(
logEvent({
variables: {
input: { type: 'SOCIAL_LINK_ADDED', meta: { platform } },
},
}),
);
}
});

return jobs;
};


const updateProfileMetadata = async (data: ProfileData) => {
setRegistrationLoading(true);

Expand All @@ -126,12 +160,13 @@ export const ProfileFormView: React.FC<ProfileFormProps> = ({

await updateUser({
variables: {
input: {
...metadata
},
input: { ...metadata },
},
});

const events = buildPerkEvents(data, profilePictureURI, initialValues ?? null);
await Promise.all(events);

setRegistrationLoading(false);
onSuccess();
} catch (error) {
Expand Down Expand Up @@ -170,12 +205,15 @@ export const ProfileFormView: React.FC<ProfileFormProps> = ({
await createUser({
variables: {
input: {
address: session?.address,
...metadata
address: session.address,
...metadata,
},
},
});

const events = buildPerkEvents(data, profilePictureURI, null);
await Promise.all(events);

setRegistrationLoading(false);
notifySuccess(SUCCESS.PROFILE_CREATED_SUCCESSFULLY);
setTimeout(refreshUser, 100)
Expand Down
95 changes: 48 additions & 47 deletions src/components/video-player/video-player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,30 @@ import useGetSubtitles from '@src/hooks/protocol/use-get-subtitles.ts';
import { useResponsive } from '@src/hooks/use-responsive';
import Label from '../label';
import {ErrorData} from "hls.js"
import { useLogEventMutation } from '@src/graphql/generated/hooks.tsx';
import { useAuth } from '@src/hooks/use-auth.ts';

export interface VideoPlayerProps {
src: string;
cid: string;
titleMovie: string;
onBack?: () => void;
showBack?: boolean;
postId: string;
}

export const VideoPlayer: FC<VideoPlayerProps> = ({ src, cid, titleMovie, onBack, showBack }) => {
const STEP = 5;

export const VideoPlayer: FC<VideoPlayerProps> = ({ src, cid, titleMovie, postId, onBack, showBack }) => {
const mdUp = useResponsive('up', 'md');
const player = useRef<MediaPlayerInstance>(null);
const controlsVisible = useMediaState('controlsVisible', player);
const { tracks, getSubtitles } = useGetSubtitles();
const [logEvent] = useLogEventMutation();
const { session } = useAuth();

const watchedSeconds = useRef<Set<number>>(new Set()) // distinct seconds already counted
const nextEvent = useRef(5) // next percentage to emit (5,10,15…)

useEffect(() => {
if (cid) getSubtitles(cid);
Expand All @@ -49,6 +59,40 @@ export const VideoPlayer: FC<VideoPlayerProps> = ({ src, cid, titleMovie, onBack
return () => document?.removeEventListener('keydown', handleKeyDown);
}, []);

const emit = async (type: string, progress?: number) => {
if (!session?.authenticated) return;
try {
console.log('emit event', type, progress);
await logEvent({ variables: { input: { type, targetId: postId, targetType: 'POST', progress, meta: { cid } } } });
} catch (e) {
console.error('logEvent error', e);
}
};

const handlePlay = () => emit('VIDEO_START');
const handleEnded = () => emit('VIDEO_WATCH_FULL');

const handleTimeUpdate = () => {
const media = player.current
if (!media?.duration || Number.isNaN(media.duration)) return

// currentTime is fractional (e.g.3.334s) floor to the integer part so we count each full second only once
const sec = Math.floor(media.currentTime)

// if this second was already handled, do nothing (saves work)
if (watchedSeconds.current.has(sec)) return
watchedSeconds.current.add(sec)

// calculate percentage watched based on unique seconds viewed
const percent = (watchedSeconds.current.size / Math.floor(media.duration)) * 100

// emit events every 5% (5,10,15…)
while (percent >= nextEvent.current) {
emit(`VIDEO_${nextEvent.current}`, nextEvent.current)
nextEvent.current += STEP
}
}

// on provider (HLS) initialization
const onProviderSetup = (provider: MediaProviderAdapter) => {
if (isHLSProvider(provider)) {
Expand All @@ -72,64 +116,18 @@ export const VideoPlayer: FC<VideoPlayerProps> = ({ src, cid, titleMovie, onBack
if (isHLSProvider(provider)) {
provider.library = Hls;
provider.config = {
// "capLevelToPlayerSize": true, // avoid more resolution if doest not fit in the current viewport
// https://github.com/video-dev/hls.js/blob/master/docs/API.md
// maxBufferLength defines the target amount of video (in seconds) the player tries to keep buffered.
// The buffer plays a crucial role in balancing playback stability and adaptive bitrate (ABR) decisions.
// A larger buffer reduces rebuffering risk but may delay quality switches, while a smaller buffer
// allows faster adaptation but increases the chance of playback interruptions.
// Finding the right balance ensures smooth playback without unnecessary network congestion.
// (hls_time = 6 + maxBufferLength = 30) = 5 fragments in buffer
"maxBufferLength": 60, // Max video buffer length in seconds
"maxMaxBufferLength": 600, // Absolute max buffer length
// maxStarvationDelay defines the maximum acceptable time (in seconds) a fragment can take to download
// while playback is already in progress.
// - If a fragment is estimated to take longer than this value and the buffer is running low,
// the player switches the best quality that matches this time constraint.
// - This ensures a continuous playback experience by adapting the quality to network conditions in real-time.
// "maxStarvationDelay": 4,
// maxLoadingDelay defines the maximum allowed time (in seconds) to load the initial fragments when starting playback.
// - The ABR controller ensures:
// - The time to fetch the first low-quality fragment (e.g., 420p)
// - + The time to fetch the estimated optimal-quality fragment (e.g., 720p)
// - is below this value.
// - If the total loading time exceeds maxLoadingDelay, the player starts with a lower quality
// to minimize startup delay and ensure fast playback.
// - Unlike maxStarvationDelay, this setting only applies at the **start** of playback,
// ensuring the video loads quickly even if it means initially using a lower quality.
// "maxLoadingDelay": 4,
// abrEwmaFastVod: Controls how quickly the algorithm reacts to bandwidth changes in VOD (Video On Demand).
// A higher value makes the algorithm less sensitive to short-term fluctuations, smoothing out rapid changes.
// Recommended range: 2.0 - 5.0 (Higher = Smoother)
// "abrEwmaFastVoD": 3,
// abrEwmaSlowVod: Controls the long-term average bandwidth estimation for adaptive bitrate switching.
// A higher value averages the bandwidth over a longer period, reducing frequent quality switches.
// Recommended range: 10.0 - 20.0 (Higher = More stable, but slower adaptation)
// "abrEwmaSlowVoD": 8,
// abrBandWidthFactor: Determines how conservatively HLS estimates available bandwidth.
// A value < 1.0 ensures HLS.js does not use the full estimated bandwidth, preventing aggressive quality changes.
// Recommended range: 0.7 - 0.9 (Lower = More cautious, fewer quality switches)
// "abrBandWidthFactor": 0.7,
// abrBandWidthUpFactor: Controls how aggressively the player upgrades to a higher bitrate.
// A lower value prevents HLS.js from switching to a higher quality too quickly, reducing unnecessary upscaling.
// Recommended range: 0.5 - 0.8 (Lower = More stable, avoids excessive upscaling)
// "abrBandWidthUpFactor": 0.6,
"enableSoftwareAES": false, // Disable software AES decryption
"enableID3MetadataCues": false, // Disable ID3 metadata cues
"enableWebVTT": true, // Enable WebVTT subtitles
"enableIMSC1": false, // Disable IMSC1 subtitles
"enableCEA708Captions": false, // Disable CEA-708 captions,
// "abrMaxWithRealBitrate": true,
"enableWorker": true,
"backBufferLength": 90,
// "progressive": true,
// "lowLatencyMode": false, // Not needed in VOD
// "startFragPrefetch": true,
"fLoader": FetchLoader,
"pLoader": XhrLoader
};


}
}

Expand All @@ -139,6 +137,9 @@ export const VideoPlayer: FC<VideoPlayerProps> = ({ src, cid, titleMovie, onBack
src={{ src, type: 'application/x-mpegurl' }}
onProviderChange={onProviderChange}
onProviderSetup={onProviderSetup}
onPlay={handlePlay}
onEnded={handleEnded}
onTimeUpdate={handleTimeUpdate}
viewType="video"
streamType="on-demand"
logLevel="warn"
Expand Down
Loading
Loading