[FEAT] update fullscreen management
This commit is contained in:
parent
ae9f00a60a
commit
57a48de52f
4
.gitignore
vendored
4
.gitignore
vendored
@ -53,6 +53,8 @@ testem.log
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
/env_dev/data
|
||||
/env_dev/dataMongo
|
||||
|
||||
backPY/env
|
||||
|
||||
@ -62,3 +64,5 @@ __pycache__
|
||||
|
||||
.design/
|
||||
.vscode/
|
||||
front/storybook-static
|
||||
back/bin
|
||||
|
80
Dockerfile
80
Dockerfile
@ -3,23 +3,31 @@
|
||||
## buyilding-end install applications:
|
||||
##
|
||||
######################################################################################
|
||||
FROM archlinux:base-devel AS builder
|
||||
FROM archlinux:base-devel AS common
|
||||
# update system
|
||||
RUN pacman -Syu --noconfirm && pacman-db-upgrade \
|
||||
&& pacman -S --noconfirm jdk-openjdk maven npm pnpm \
|
||||
&& pacman -S --noconfirm jdk-openjdk wget\
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
FROM common AS builder
|
||||
# update system
|
||||
RUN pacman -Syu --noconfirm && pacman-db-upgrade \
|
||||
&& pacman -S --noconfirm maven npm pnpm \
|
||||
&& pacman -Scc --noconfirm
|
||||
|
||||
ENV PATH /tmp/node_modules/.bin:$PATH
|
||||
WORKDIR /tmp
|
||||
|
||||
######################################################################################
|
||||
##
|
||||
## Build back:
|
||||
##
|
||||
######################################################################################
|
||||
FROM builder AS buildBack
|
||||
COPY back/pom.xml /tmp
|
||||
COPY back/src /tmp/src/
|
||||
FROM builder AS build_back
|
||||
COPY back/pom.xml ./
|
||||
COPY back/Formatter.xml ./
|
||||
COPY back/src ./src/
|
||||
RUN mvn clean compile assembly:single
|
||||
|
||||
######################################################################################
|
||||
@ -27,27 +35,44 @@ RUN mvn clean compile assembly:single
|
||||
## Build front:
|
||||
##
|
||||
######################################################################################
|
||||
FROM builder AS buildFront
|
||||
FROM builder AS dependency_front
|
||||
|
||||
RUN echo "@kangaroo-and-rabbit:registry=https://gitea.atria-soft.org/api/packages/kangaroo-and-rabbit/npm/" > /root/.npmrc
|
||||
|
||||
ADD front/package.json \
|
||||
front/karma.conf.js \
|
||||
front/protractor.conf.js \
|
||||
/tmp/
|
||||
front/pnpm-lock.yaml \
|
||||
./
|
||||
ADD front/src/theme ./src/theme
|
||||
|
||||
# install and cache app dependencies
|
||||
RUN pnpm install
|
||||
RUN pnpm install --prod=false
|
||||
|
||||
ADD front/e2e \
|
||||
front/tsconfig.json \
|
||||
front/tslint.json \
|
||||
front/angular.json \
|
||||
/tmp/
|
||||
ADD front/src /tmp/src
|
||||
###############################################################
|
||||
## Install sources
|
||||
###############################################################
|
||||
FROM dependency_front AS load_sources_front
|
||||
|
||||
# generate build
|
||||
RUN ng build --output-path=dist --configuration=production --base-href=/karideo/ --deploy-url=/karideo/
|
||||
# JUST to get the vertion of the application and his sha...
|
||||
COPY \
|
||||
front/tsconfig.json \
|
||||
front/tsconfig.node.json \
|
||||
front/vite.config.mts \
|
||||
front/index.html \
|
||||
./
|
||||
|
||||
COPY front/public ./public
|
||||
COPY front/src ./src
|
||||
|
||||
#We are not in prod mode ==> we need to overwrite the production env.
|
||||
ARG env=front/.env.production
|
||||
COPY ${env} .env
|
||||
|
||||
###############################################################
|
||||
## Build the sources
|
||||
###############################################################
|
||||
FROM load_sources_front AS build_front
|
||||
# build in bundle mode all the application
|
||||
RUN pnpm static:build
|
||||
|
||||
######################################################################################
|
||||
##
|
||||
@ -55,17 +80,22 @@ RUN ng build --output-path=dist --configuration=production --base-href=/karideo/
|
||||
##
|
||||
######################################################################################
|
||||
|
||||
FROM bellsoft/liberica-openjdk-alpine:latest
|
||||
# add wget to manage the health check...
|
||||
RUN apk add --no-cache wget
|
||||
#FROM bellsoft/liberica-openjdk-alpine:latest
|
||||
## add wget to manage the health check...
|
||||
#RUN apk add --no-cache wget
|
||||
FROM common
|
||||
|
||||
ENV LANG=C.UTF-8
|
||||
ENV LANG C.UTF-8
|
||||
|
||||
COPY --from=buildBack /tmp/out/maven/*.jar /application/application.jar
|
||||
COPY --from=buildFront /tmp/dist /application/front/
|
||||
COPY --from=build_back /tmp/out/maven/*.jar /application/application.jar
|
||||
COPY --from=build_front /tmp/dist /application/front/
|
||||
|
||||
WORKDIR /application/
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
# To verify health-check: docker inspect --format "{{json .State.Health }}" YOUR_SERVICE_NAME | jq
|
||||
HEALTHCHECK --start-period=10s --start-interval=2s --interval=30s --timeout=5s --retries=10 \
|
||||
CMD wget --no-verbose --tries=1 --spider http://localhost:80/api/health_check || exit 1
|
||||
|
||||
CMD ["java", "-Xms64M", "-Xmx1G", "-cp", "/application/application.jar", "org.kar.karideo.WebLauncher"]
|
||||
|
125
README.md
Normal file
125
README.md
Normal file
@ -0,0 +1,125 @@
|
||||
Karideo
|
||||
=======
|
||||
|
||||
**K**angaroo **A**nd **R**abbit (v)ideo is a simple framework to propose video streaming for personal network
|
||||
|
||||
Run in local:
|
||||
=============
|
||||
|
||||
Start tools
|
||||
-----------
|
||||
|
||||
Start the server basic interfaces: (DB(mySQL), Adminer)
|
||||
|
||||
```{.bash}
|
||||
# start the Bdd interface (no big data > 50Mo)
|
||||
docker compose -f env_dev/docker-compose.yaml up -d
|
||||
```
|
||||
|
||||
Start the Back-end:
|
||||
-------------------
|
||||
|
||||
backend is developed in JAVA
|
||||
|
||||
The first step is configuring your JAVA version (or select the JVM with the OS)
|
||||
```bash
|
||||
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
|
||||
```
|
||||
|
||||
Install the dependency:
|
||||
```bash
|
||||
mvn install
|
||||
```
|
||||
|
||||
Run the test
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
|
||||
Install it for external use
|
||||
```bash
|
||||
mvn install
|
||||
```
|
||||
|
||||
Execute the local server:
|
||||
```bash
|
||||
mvn exec:java@dev-mode
|
||||
```
|
||||
|
||||
Start the Front-end:
|
||||
--------------------
|
||||
|
||||
backend is developed in JAVA
|
||||
```bash
|
||||
cd front
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Display the result:
|
||||
-------------------
|
||||
|
||||
[show the webpage: http://localhost:4203](http://localhost:4203)
|
||||
|
||||
Some other dev tools:
|
||||
=====================
|
||||
|
||||
Format code:
|
||||
------------
|
||||
|
||||
```bash
|
||||
export PATH=$(ls -d --color=never /usr/lib/jvm/java-2*-openjdk)/bin:$PATH
|
||||
mvn formatter:format
|
||||
mvn test
|
||||
```
|
||||
|
||||
Tools in production mode
|
||||
========================
|
||||
|
||||
Changing the Log Level
|
||||
----------------------
|
||||
|
||||
In a production environment, you can adjust the log level to help diagnose bugs more effectively.
|
||||
|
||||
The available log levels are:
|
||||
| **Log Level Tag** | **org.kar.karideo** | **org.kar.archidata** | **other** |
|
||||
| ----------------- | ------------------- | --------------------- | --------- |
|
||||
| `prod` | INFO | INFO | INFO |
|
||||
| `prod-debug` | DEBUG | INFO | INFO |
|
||||
| `prod-trace` | TRACE | DEBUG | INFO |
|
||||
| `prod-trace-full` | TRACE | TRACE | INFO |
|
||||
| `dev` | TRACE | DEBUG | INFO |
|
||||
|
||||
|
||||
Manual set in production:
|
||||
=========================
|
||||
|
||||
Connect on the registry
|
||||
------------------------
|
||||
|
||||
To log-in and log-out from the registry:
|
||||
```bash
|
||||
export REGISTRY_ADDRESS=gitea.atria-soft.org
|
||||
docker login -u <<YOUR_USER_NAME>> ${REGISTRY_ADDRESS}
|
||||
docker logout ${REGISTRY_ADDRESS}
|
||||
```
|
||||
|
||||
pull the root image of dockers
|
||||
------------------------------
|
||||
|
||||
```bash
|
||||
docker pull archlinux:base-devel
|
||||
docker pull bellsoft/liberica-openjdk-alpine:latest
|
||||
```
|
||||
|
||||
Create the version
|
||||
------------------
|
||||
|
||||
Execute in the local folder: (use ```dev``` for development and ```latest``` for production release)
|
||||
|
||||
```bash
|
||||
export TAG_DOCKER=latest
|
||||
export REGISTRY_ADDRESS=gitea.atria-soft.org
|
||||
docker build -t ${REGISTRY_ADDRESS}/kangaroo-and-rabbit/karideo:${TAG_DOCKER} .
|
||||
docker push ${REGISTRY_ADDRESS}/kangaroo-and-rabbit/karideo:${TAG_DOCKER}
|
||||
```
|
@ -1,2 +1,2 @@
|
||||
# URL for database connection
|
||||
VITE_API_BASE_URL=karusic/api/
|
||||
VITE_API_BASE_URL=karideo/api/
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ErrorBoundary } from '@/errors/ErrorBoundary';
|
||||
|
||||
import { AudioPlayer } from './components';
|
||||
import { VideoPlayer } from './components';
|
||||
import { EnvDevelopment } from './components/EnvDevelopment/EnvDevelopment';
|
||||
import { AppRoutes } from './scene/AppRoutes';
|
||||
import { ServiceContextProvider } from './service/ServiceContext';
|
||||
@ -12,7 +12,7 @@ export const App = () => {
|
||||
<ErrorBoundary>
|
||||
<AppRoutes />
|
||||
</ErrorBoundary>
|
||||
<AudioPlayer />
|
||||
<VideoPlayer />
|
||||
</ServiceContextProvider>
|
||||
);
|
||||
};
|
||||
|
@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Interface of the server (auto-generated code)
|
||||
*/
|
||||
import { z as zod } from "zod";
|
||||
|
||||
import {ZodLong} from "./long";
|
||||
import {ZodUUIDGenericDataSoftDelete, ZodUUIDGenericDataSoftDeleteWrite } from "./uuid-generic-data-soft-delete";
|
||||
|
||||
export const ZodData = ZodUUIDGenericDataSoftDelete.extend({
|
||||
/**
|
||||
* Sha512 of the data
|
||||
*/
|
||||
sha512: zod.string().max(128),
|
||||
/**
|
||||
* Mime -type of the media
|
||||
*/
|
||||
mimeType: zod.string().max(128),
|
||||
/**
|
||||
* Size in Byte of the data
|
||||
*/
|
||||
size: ZodLong,
|
||||
|
||||
});
|
||||
|
||||
export type Data = zod.infer<typeof ZodData>;
|
||||
|
||||
export function isData(data: any): data is Data {
|
||||
try {
|
||||
ZodData.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodData' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export const ZodDataWrite = ZodUUIDGenericDataSoftDeleteWrite.extend({
|
||||
/**
|
||||
* Sha512 of the data
|
||||
*/
|
||||
sha512: zod.string().max(128).optional(),
|
||||
/**
|
||||
* Mime -type of the media
|
||||
*/
|
||||
mimeType: zod.string().max(128).optional(),
|
||||
/**
|
||||
* Size in Byte of the data
|
||||
*/
|
||||
size: ZodLong.optional(),
|
||||
|
||||
});
|
||||
|
||||
export type DataWrite = zod.infer<typeof ZodDataWrite>;
|
||||
|
||||
export function isDataWrite(data: any): data is DataWrite {
|
||||
try {
|
||||
ZodDataWrite.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodDataWrite' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
* Interface of the server (auto-generated code)
|
||||
*/
|
||||
import { z as zod } from "zod";
|
||||
|
||||
import {ZodUUIDGenericData, ZodUUIDGenericDataWrite } from "./uuid-generic-data";
|
||||
|
||||
export const ZodUUIDGenericDataSoftDelete = ZodUUIDGenericData.extend({
|
||||
/**
|
||||
* Deleted state
|
||||
*/
|
||||
deleted: zod.boolean().readonly().optional(),
|
||||
|
||||
});
|
||||
|
||||
export type UUIDGenericDataSoftDelete = zod.infer<typeof ZodUUIDGenericDataSoftDelete>;
|
||||
|
||||
export function isUUIDGenericDataSoftDelete(data: any): data is UUIDGenericDataSoftDelete {
|
||||
try {
|
||||
ZodUUIDGenericDataSoftDelete.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodUUIDGenericDataSoftDelete' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export const ZodUUIDGenericDataSoftDeleteWrite = ZodUUIDGenericDataWrite.extend({
|
||||
|
||||
});
|
||||
|
||||
export type UUIDGenericDataSoftDeleteWrite = zod.infer<typeof ZodUUIDGenericDataSoftDeleteWrite>;
|
||||
|
||||
export function isUUIDGenericDataSoftDeleteWrite(data: any): data is UUIDGenericDataSoftDeleteWrite {
|
||||
try {
|
||||
ZodUUIDGenericDataSoftDeleteWrite.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodUUIDGenericDataSoftDeleteWrite' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/**
|
||||
* Interface of the server (auto-generated code)
|
||||
*/
|
||||
import { z as zod } from "zod";
|
||||
|
||||
import {ZodUUID} from "./uuid";
|
||||
import {ZodGenericTiming, ZodGenericTimingWrite } from "./generic-timing";
|
||||
|
||||
export const ZodUUIDGenericData = ZodGenericTiming.extend({
|
||||
/**
|
||||
* Unique UUID of the object
|
||||
*/
|
||||
uuid: ZodUUID.readonly(),
|
||||
|
||||
});
|
||||
|
||||
export type UUIDGenericData = zod.infer<typeof ZodUUIDGenericData>;
|
||||
|
||||
export function isUUIDGenericData(data: any): data is UUIDGenericData {
|
||||
try {
|
||||
ZodUUIDGenericData.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodUUIDGenericData' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export const ZodUUIDGenericDataWrite = ZodGenericTimingWrite.extend({
|
||||
|
||||
});
|
||||
|
||||
export type UUIDGenericDataWrite = zod.infer<typeof ZodUUIDGenericDataWrite>;
|
||||
|
||||
export function isUUIDGenericDataWrite(data: any): data is UUIDGenericDataWrite {
|
||||
try {
|
||||
ZodUUIDGenericDataWrite.parse(data);
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
console.log(`Fail to parse data type='ZodUUIDGenericDataWrite' error=${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
|
||||
import { Box, BoxProps, Flex, FlexProps } from '@chakra-ui/react';
|
||||
import { Image } from '@chakra-ui/react';
|
||||
import { Box, BoxProps, Flex, Image } from '@chakra-ui/react';
|
||||
|
||||
import { ObjectId } from '@/back-api';
|
||||
import { DataUrlAccess } from '@/utils/data-url-access';
|
||||
@ -17,6 +16,7 @@ export type CoversProps = Omit<BoxProps, 'iconEmpty'> & {
|
||||
|
||||
export const Covers = ({
|
||||
data,
|
||||
onClick,
|
||||
iconEmpty,
|
||||
size = '100px',
|
||||
slideshow = false,
|
||||
@ -45,7 +45,7 @@ export const Covers = ({
|
||||
|
||||
if (!data || data.length < 1) {
|
||||
if (iconEmpty) {
|
||||
return <Icon children={iconEmpty} sizeIcon={size} />;
|
||||
return <Icon children={iconEmpty} sizeIcon={size} onClick={onClick} />;
|
||||
} else {
|
||||
return (
|
||||
<Box
|
||||
@ -56,6 +56,7 @@ export const Covers = ({
|
||||
borderColor="blue"
|
||||
borderWidth="1px"
|
||||
margin="auto"
|
||||
onClick={onClick}
|
||||
{...rest}
|
||||
></Box>
|
||||
);
|
||||
@ -69,6 +70,7 @@ export const Covers = ({
|
||||
src={url}
|
||||
maxWidth={size}
|
||||
boxSize={size} /*{...rest}*/
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ export const EmptyEnd = () => {
|
||||
<Box
|
||||
width="full"
|
||||
height="25%"
|
||||
minHeight="250px"
|
||||
minHeight="calc(max(50vh,500px))"
|
||||
// borderWidth="1px"
|
||||
// borderColor="red"
|
||||
></Box>
|
||||
|
@ -1,18 +1,21 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Box, Flex, IconButton, SliderTrack, Text } from '@chakra-ui/react';
|
||||
import { Box, chakra, Flex, IconButton, SliderTrack, Spacer, Text, useBreakpointValue } from '@chakra-ui/react';
|
||||
import {
|
||||
MdFastForward,
|
||||
MdFastRewind,
|
||||
MdFullscreen,
|
||||
MdLooksOne,
|
||||
MdNavigateBefore,
|
||||
MdNavigateNext,
|
||||
MdOutlinePictureInPictureAlt,
|
||||
MdPause,
|
||||
MdPictureInPictureAlt,
|
||||
MdPlayArrow,
|
||||
MdRepeat,
|
||||
MdRepeatOne,
|
||||
MdStop,
|
||||
MdTrendingFlat,
|
||||
MdTrendingFlat
|
||||
} from 'react-icons/md';
|
||||
|
||||
import { useColorModeValue } from '@/components/ui/color-mode';
|
||||
@ -59,10 +62,44 @@ const formatTime = (time) => {
|
||||
return '00:00';
|
||||
};
|
||||
|
||||
export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
const { playMediaList, MediaOffset, previous, next, first } =
|
||||
export const VideoPlayer = ({}: AudioPlayerProps) => {
|
||||
const [time, setTime] = useState(10);
|
||||
const [isRunning, setIsRunning] = useState(true);
|
||||
useEffect(() => {
|
||||
if (!isRunning || time <= 0) {
|
||||
console.log(`exit timer`);
|
||||
setIsRunning(false);
|
||||
return;
|
||||
}
|
||||
const timer = setInterval(() => {
|
||||
setTime((prevTime) => {
|
||||
console.log(`current time : ${prevTime}`);
|
||||
return prevTime - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, [time, isRunning]);
|
||||
|
||||
const resetTimer = () => {
|
||||
setTime(10);
|
||||
setIsRunning(true);
|
||||
};
|
||||
useEffect(() => {
|
||||
const resetTimer = () => {
|
||||
setTime(10);
|
||||
setIsRunning(true);
|
||||
};
|
||||
window.addEventListener("mousemove", resetTimer);
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", resetTimer);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const { playMediaList, MediaOffset, previous, next, first, clear } =
|
||||
useActivePlaylistService();
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const [isPlaying, setIsPlaying] = useState<boolean>(false);
|
||||
const [timeProgress, setTimeProgress] = useState<number>(0);
|
||||
const [playingMode, setPlayingMode] = useState<PlayMode>(PlayMode.PLAY_ALL);
|
||||
@ -74,6 +111,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
const { dataType } = useSpecificType(dataMedia?.typeId);
|
||||
const { dataSeries } = useSpecificSeries(dataMedia?.seriesId);
|
||||
|
||||
const isMobile = useBreakpointValue({ base: false, sm: true });
|
||||
const [mediaSource, setMediaSource] = useState<string>('');
|
||||
useEffect(() => {
|
||||
setMediaSource(
|
||||
@ -83,7 +121,16 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
);
|
||||
}, [dataMedia, setMediaSource]);
|
||||
const backColor = useColorModeValue('back.100', 'back.800');
|
||||
const configButton = {
|
||||
const configButton = isMobile ? {
|
||||
borderRadius: 'full',
|
||||
backgroundColor: 'transparent',
|
||||
_hover: {
|
||||
bgColor: 'brand.500',
|
||||
},
|
||||
width: '35px',
|
||||
height: '35px',
|
||||
padding: '2px',
|
||||
} : {
|
||||
borderRadius: 'full',
|
||||
backgroundColor: 'transparent',
|
||||
_hover: {
|
||||
@ -95,15 +142,15 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
if (!videoRef || !videoRef.current) {
|
||||
return;
|
||||
}
|
||||
if (isPlaying) {
|
||||
audioRef.current.play();
|
||||
videoRef.current.play();
|
||||
} else {
|
||||
audioRef.current.pause();
|
||||
videoRef.current.pause();
|
||||
}
|
||||
}, [isPlaying, audioRef]);
|
||||
}, [isPlaying, videoRef]);
|
||||
|
||||
const onAudioEnded = () => {
|
||||
if (playMediaList.length === 0 || isNullOrUndefined(MediaOffset)) {
|
||||
@ -124,46 +171,46 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
};
|
||||
const onSeek = (newValue) => {
|
||||
console.log(`onSeek: ${newValue}`);
|
||||
if (!audioRef || !audioRef.current) {
|
||||
if (!videoRef || !videoRef.current) {
|
||||
return;
|
||||
}
|
||||
audioRef.current.currentTime = newValue;
|
||||
videoRef.current.currentTime = newValue;
|
||||
};
|
||||
const onPlay = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
if (!videoRef || !videoRef.current) {
|
||||
return;
|
||||
}
|
||||
if (isPlaying) {
|
||||
audioRef.current.pause();
|
||||
videoRef.current.pause();
|
||||
} else {
|
||||
audioRef.current.play();
|
||||
videoRef.current.play();
|
||||
}
|
||||
};
|
||||
const onStop = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
if (!videoRef || !videoRef.current) {
|
||||
return;
|
||||
}
|
||||
if (audioRef.current.currentTime == 0 && audioRef.current.paused) {
|
||||
// TODO remove current playing value
|
||||
if (videoRef.current.currentTime <= 0.5 && videoRef.current.paused) {
|
||||
clear();
|
||||
} else {
|
||||
audioRef.current.pause();
|
||||
audioRef.current.currentTime = 0;
|
||||
videoRef.current.pause();
|
||||
videoRef.current.currentTime = 0;
|
||||
}
|
||||
};
|
||||
const onNavigatePrevious = () => {
|
||||
previous();
|
||||
};
|
||||
const onFastRewind = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
if (!videoRef || !videoRef.current) {
|
||||
return;
|
||||
}
|
||||
audioRef.current.currentTime -= 10;
|
||||
videoRef.current.currentTime -= 10;
|
||||
};
|
||||
const onFastForward = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
if (!videoRef || !videoRef.current) {
|
||||
return;
|
||||
}
|
||||
audioRef.current.currentTime += 10;
|
||||
videoRef.current.currentTime += 10;
|
||||
};
|
||||
const onNavigateNext = () => {
|
||||
next();
|
||||
@ -185,17 +232,17 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
* Call when meta-data is updated
|
||||
*/
|
||||
function onChangeMetadata(): void {
|
||||
const seconds = audioRef.current?.duration;
|
||||
const seconds = videoRef.current?.duration;
|
||||
if (seconds !== undefined) {
|
||||
setDuration(seconds);
|
||||
}
|
||||
}
|
||||
const onTimeUpdate = () => {
|
||||
if (!audioRef || !audioRef.current) {
|
||||
if (!videoRef || !videoRef.current) {
|
||||
return;
|
||||
}
|
||||
console.log(`onTimeUpdate ${audioRef.current.currentTime}`);
|
||||
setTimeProgress(audioRef.current.currentTime);
|
||||
//console.log(`onTimeUpdate ${videoRef.current.currentTime}`);
|
||||
setTimeProgress(videoRef.current.currentTime);
|
||||
};
|
||||
const onDurationChange = (event) => {};
|
||||
const onChangeStateToPlay = () => {
|
||||
@ -212,12 +259,57 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const onFullScreen = () => {
|
||||
if (containerRef.current) {
|
||||
if (!document.fullscreenElement) {
|
||||
if (containerRef.current.requestFullscreen) {
|
||||
resetTimer();
|
||||
containerRef.current.requestFullscreen();
|
||||
}
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
const [isPiPSupported, setIsPiPSupported] = useState(false);
|
||||
useEffect(() => {
|
||||
setIsPiPSupported(!!document.pictureInPictureEnabled);
|
||||
}, []);
|
||||
const [isPiP, setIsPiP] = useState(false);
|
||||
const onPictureInPicture = async () => {
|
||||
if (videoRef.current) {
|
||||
try {
|
||||
if (!isPiP) {
|
||||
await videoRef.current.requestPictureInPicture();
|
||||
setIsPiP(true);
|
||||
} else {
|
||||
document.exitPictureInPicture();
|
||||
setIsPiP(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur avec Picture-in-Picture:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
useEffect(() => {
|
||||
const handleFullScreenChange = () => {
|
||||
console.log(`changeFullScreen: ${!!document.fullscreenElement}`)
|
||||
setIsFullScreen(!!document.fullscreenElement);
|
||||
};
|
||||
|
||||
document.addEventListener("fullscreenchange", handleFullScreenChange);
|
||||
return () => {
|
||||
document.removeEventListener("fullscreenchange", handleFullScreenChange);
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
{!isNullOrUndefined(MediaOffset) && (
|
||||
<>
|
||||
<Flex
|
||||
position="absolute"
|
||||
height="150px"
|
||||
//height="500px"
|
||||
minHeight="150px"
|
||||
paddingY="5px"
|
||||
paddingX="10px"
|
||||
@ -228,11 +320,30 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
zIndex={1000}
|
||||
borderWidth="1px"
|
||||
borderColor="brand.900"
|
||||
bgColor={backColor}
|
||||
bgColor={isFullScreen ? "0x000000" : backColor}
|
||||
borderTopRadius="10px"
|
||||
direction="column"
|
||||
>
|
||||
<Text
|
||||
ref={containerRef}
|
||||
>
|
||||
<chakra.video
|
||||
position={isFullScreen ? "absolute" : "relative"}
|
||||
maxHeight={isFullScreen ? undefined : "30vh"}
|
||||
maxWidth={isFullScreen ? undefined : "100%"}
|
||||
height={isFullScreen ? "100%" : undefined}
|
||||
width={isFullScreen ? "100%": undefined}
|
||||
marginX="auto"
|
||||
src={mediaSource}
|
||||
ref={videoRef}
|
||||
//preload={true}
|
||||
onPlay={onChangeStateToPlay}
|
||||
onPause={onChangeStateToPause}
|
||||
onTimeUpdate={onTimeUpdate}
|
||||
onDurationChange={onDurationChange}
|
||||
onLoadedMetadata={onChangeMetadata}
|
||||
autoPlay={true}
|
||||
onEnded={onAudioEnded}
|
||||
/>
|
||||
{(!isFullScreen || (!isMobile || isRunning)) && <><Text
|
||||
alignContent="left"
|
||||
fontSize="20px"
|
||||
fontWeight="bold"
|
||||
@ -240,7 +351,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
// noOfLines={1}
|
||||
>
|
||||
>
|
||||
{dataMedia?.name ?? '???'}
|
||||
</Text>
|
||||
<Text
|
||||
@ -250,11 +361,12 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
// noOfLines={1}
|
||||
>
|
||||
>
|
||||
{dataSeries && dataSeries.name}
|
||||
{dataSeason && dataSeason?.name}
|
||||
{dataType && ` / ${dataType.name}`}
|
||||
</Text>
|
||||
{isFullScreen && <Spacer/>}
|
||||
<Box width="full" paddingX="15px">
|
||||
<Slider
|
||||
defaultValue={[0]}
|
||||
@ -267,7 +379,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
colorPalette="brand"
|
||||
marks={marks()}
|
||||
//focusCapture={false}
|
||||
>
|
||||
>
|
||||
<SliderTrack
|
||||
bg="brand.200"
|
||||
height="10px"
|
||||
@ -283,7 +395,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
marginRight="auto"
|
||||
overflow="hidden"
|
||||
// noOfLines={1}
|
||||
>
|
||||
>
|
||||
{formatTime(timeProgress)}
|
||||
</Text>
|
||||
<Text alignContent="left" fontSize="16px" userSelect="none">
|
||||
@ -308,7 +420,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
aria-label={'Stop'}
|
||||
onClick={onStop}
|
||||
variant="ghost"
|
||||
>
|
||||
>
|
||||
<MdStop style={{ width: '100%', height: '100%' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
@ -317,7 +429,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
onClick={onNavigatePrevious}
|
||||
marginLeft="auto"
|
||||
variant="ghost"
|
||||
>
|
||||
>
|
||||
<MdNavigateBefore style={{ width: '100%', height: '100%' }} />{' '}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
@ -325,7 +437,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
aria-label={'jump 15sec in past'}
|
||||
onClick={onFastRewind}
|
||||
variant="ghost"
|
||||
>
|
||||
>
|
||||
<MdFastRewind style={{ width: '100%', height: '100%' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
@ -333,7 +445,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
aria-label={'jump 15sec in future'}
|
||||
onClick={onFastForward}
|
||||
variant="ghost"
|
||||
>
|
||||
>
|
||||
<MdFastForward style={{ width: '100%', height: '100%' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
@ -342,7 +454,7 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
marginRight="auto"
|
||||
onClick={onNavigateNext}
|
||||
variant="ghost"
|
||||
>
|
||||
>
|
||||
<MdNavigateNext style={{ width: '100%', height: '100%' }} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
@ -350,25 +462,32 @@ export const AudioPlayer = ({}: AudioPlayerProps) => {
|
||||
aria-label={'continue to the end'}
|
||||
onClick={onTypePlay}
|
||||
variant="ghost"
|
||||
>
|
||||
>
|
||||
{playModeIcon[playingMode]}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Fullscreen'}
|
||||
onClick={onFullScreen}
|
||||
variant="ghost"
|
||||
>
|
||||
<MdFullscreen style={{ width: '100%', height: '100%' }} />
|
||||
</IconButton>
|
||||
{isPiPSupported && !isMobile &&
|
||||
<IconButton
|
||||
{...configButton}
|
||||
aria-label={'Fullscreen'}
|
||||
onClick={onPictureInPicture}
|
||||
variant="ghost"
|
||||
>
|
||||
{isPiP ? <MdOutlinePictureInPictureAlt style={{ width: '100%', height: '100%' }} /> : <MdPictureInPictureAlt style={{ width: '100%', height: '100%' }} /> }
|
||||
</IconButton>
|
||||
}
|
||||
</Flex>
|
||||
</>}
|
||||
</Flex>
|
||||
</>
|
||||
)}
|
||||
|
||||
<audio
|
||||
src={mediaSource}
|
||||
ref={audioRef}
|
||||
//preload={true}
|
||||
onPlay={onChangeStateToPlay}
|
||||
onPause={onChangeStateToPause}
|
||||
onTimeUpdate={onTimeUpdate}
|
||||
onDurationChange={onDurationChange}
|
||||
onLoadedMetadata={onChangeMetadata}
|
||||
autoPlay={true}
|
||||
onEnded={onAudioEnded}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -17,16 +17,15 @@ export const VignetteDetail = ({ icon, text, ...rest }: VignetteProps) => {
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
|
||||
key={data.id}
|
||||
padding="5px"
|
||||
as="button"
|
||||
_hover={{
|
||||
boxShadow: 'outline-over',
|
||||
bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
onClick={() => onSelectItem(data.id)}
|
||||
//onClick={() => onSelectItem(data.id)}
|
||||
>
|
||||
<DisplayType dataType={data} />
|
||||
{/* <DisplayType dataType={data} /> */}
|
||||
</Flex>
|
||||
// <Flex
|
||||
// align="flex-start"
|
||||
|
@ -1,5 +1,6 @@
|
||||
export * from './AudioPlayer';
|
||||
export * from './Cover';
|
||||
export * from './EmptyEnd';
|
||||
export * from './Icon';
|
||||
export * from './SearchInput';
|
||||
export * from './VideoPlayer';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { Flex, Spacer, Text } from '@chakra-ui/react';
|
||||
import { LuMusic2, LuPlay } from 'react-icons/lu';
|
||||
|
||||
import { Media } from '@/back-api';
|
||||
@ -45,6 +45,7 @@ export const DisplayMediaFull = ({
|
||||
overflowX="hidden"
|
||||
onClick={onClick}
|
||||
>
|
||||
<Spacer/>
|
||||
<Text
|
||||
as="span"
|
||||
alignContent="left"
|
||||
|
@ -45,7 +45,7 @@ const environment_local: Environment = {
|
||||
ssoSignUp: `${serverSSOAddress}/karso/signup/karideo-dev/`,
|
||||
ssoSignOut: `${serverSSOAddress}/karso/signout/karideo-dev/`,
|
||||
tokenStoredInPermanentStorage: false,
|
||||
replaceDataToRealServer: false,
|
||||
replaceDataToRealServer: true,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -12,13 +12,13 @@ import { HomePage } from '@/scene/home/HomePage';
|
||||
import { SSORoutes } from '@/scene/sso/SSORoutes';
|
||||
import { useHasRight } from '@/service/session';
|
||||
|
||||
import { AddPage } from './home/AddPage';
|
||||
import { SettingsPage } from './home/SettingsPage';
|
||||
import { MediaRoutes } from './media/MediaRoutes';
|
||||
import { OnAirPage } from './onAir/OnAirPage';
|
||||
import { SeasonRoutes } from './season/SeasonRoutes';
|
||||
import { SeriesRoutes } from './series/SeriesRoutes';
|
||||
import { TypeRoutes } from './type/TypesRoutes';
|
||||
import { AddPage } from './home/AddPage';
|
||||
import { SettingsPage } from './home/SettingsPage';
|
||||
import { OnAirPage } from './onAir/OnAirPage';
|
||||
import { MediaRoutes } from './media/MediaRoutes';
|
||||
|
||||
export const AppRoutes = () => {
|
||||
const { isReadable } = useHasRight('USER');
|
||||
@ -45,6 +45,7 @@ export const AppRoutes = () => {
|
||||
<Route path="Media/*" element={<MediaRoutes />} />
|
||||
<Route path="*" element={<Error404 />} />
|
||||
</>
|
||||
|
||||
) : (
|
||||
<Route path="*" element={<Error401 />} />
|
||||
)}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { Box, Button, Flex, Text } from '@chakra-ui/react';
|
||||
import { LuDisc3 } from 'react-icons/lu';
|
||||
import { MdAdd, MdEdit } from 'react-icons/md';
|
||||
import { MdEdit } from 'react-icons/md';
|
||||
import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { DisplayMediaFull } from '@/components/media/DisplayMediaFull';
|
||||
import { SeasonEditPopUp } from '@/components/popup/AlbumEditPopUp';
|
||||
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp';
|
||||
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
|
||||
@ -107,7 +106,7 @@ export const SeasonDetailPage = () => {
|
||||
bgColor: useColorModeValue('#FFFFFFF7', '#000000F7'),
|
||||
}}
|
||||
>
|
||||
<DisplayMediaFull
|
||||
{/* <DisplaySeasonFull
|
||||
media={{...data, dataId: "0-not-defined"}}
|
||||
onClick={() => onSelectItem(data.id)}
|
||||
contextMenu={[
|
||||
@ -125,7 +124,7 @@ export const SeasonDetailPage = () => {
|
||||
},
|
||||
]}
|
||||
data-testid="Season-detail-page_display-detail"
|
||||
/>
|
||||
/> */}
|
||||
</Box>
|
||||
))}
|
||||
<EmptyEnd />
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { Box, Button, Flex, Text } from '@chakra-ui/react';
|
||||
import { Button, Flex, Text } from '@chakra-ui/react';
|
||||
import { LuDisc3 } from 'react-icons/lu';
|
||||
import { MdAdd, MdEdit, MdPerson } from 'react-icons/md';
|
||||
import { MdEdit, MdPerson } from 'react-icons/md';
|
||||
import { Route, Routes, useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { Covers } from '@/components/Cover';
|
||||
import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { DisplayMedia } from '@/components/media/DisplayMedia';
|
||||
import { SeasonEditPopUp } from '@/components/popup/AlbumEditPopUp';
|
||||
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp';
|
||||
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
|
||||
import { useColorModeValue } from '@/components/ui/color-mode';
|
||||
import { useActivePlaylistService } from '@/service/ActivePlaylist';
|
||||
import { useSeasonVideo, useSpecificSeason } from '@/service/Season';
|
||||
import { useSpecificSeries } from '@/service/Series';
|
||||
@ -112,7 +110,7 @@ export const SeriesSeasonDetailPage = () => {
|
||||
padding="20px"
|
||||
width="80%"
|
||||
>
|
||||
{seasonVideo?.map((data) => (
|
||||
{/* {seasonVideo?.map((data) => (
|
||||
<Box
|
||||
minWidth="100%"
|
||||
height="60px"
|
||||
@ -147,7 +145,7 @@ export const SeriesSeasonDetailPage = () => {
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
))} */}
|
||||
<EmptyEnd />
|
||||
</Flex>
|
||||
<Routes>
|
||||
|
@ -45,8 +45,8 @@ export const TypesPage = () => {
|
||||
{dataTypes.map((data) => (
|
||||
<Flex
|
||||
align="flex-start"
|
||||
width="200px"
|
||||
height="280px"
|
||||
width={{ base: "200px", sm: "160px" }}
|
||||
height={{ base: "280px", sm: "200px" }}
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
|
||||
|
@ -38,6 +38,7 @@ export const TypesSeriesDetailPage = () => {
|
||||
const onSelectItem = (mediaId: number) => {
|
||||
let currentPlay = 0;
|
||||
const listMediaId: number[] = [];
|
||||
console.log(`select item:${mediaId}`);
|
||||
if (!videoWithType) {
|
||||
console.log('Fail to get Type...');
|
||||
return;
|
||||
@ -55,7 +56,7 @@ export const TypesSeriesDetailPage = () => {
|
||||
if (!dataType) {
|
||||
return (
|
||||
<>
|
||||
<TopBar title="Type detail" />
|
||||
<TopBar title="Series detail" />
|
||||
<PageLayoutInfoCenter>
|
||||
Fail to load Series id: {typeId}/{seriesId}
|
||||
</PageLayoutInfoCenter>
|
||||
@ -149,7 +150,7 @@ export const TypesSeriesDetailPage = () => {
|
||||
<Box
|
||||
key={data.id}
|
||||
width="200px"
|
||||
height="280px"
|
||||
height="300px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
backgroundColor={useColorModeValue('#FFFFFF88', '#00000088')}
|
||||
|
@ -8,7 +8,7 @@ import { EmptyEnd } from '@/components/EmptyEnd';
|
||||
import { PageLayout } from '@/components/Layout/PageLayout';
|
||||
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
|
||||
import { BUTTON_TOP_BAR_PROPERTY, TopBar } from '@/components/TopBar/TopBar';
|
||||
import { DisplayMediaFull } from '@/components/media/DisplayMediaFull';
|
||||
import { DisplayMediaListFull } from '@/components/media/DisplayMediaListFull';
|
||||
import { TypeEditPopUp } from '@/components/popup/GenderEditPopUp';
|
||||
import { MediaEditPopUp } from '@/components/popup/TrackEditPopUp';
|
||||
import { useColorModeValue } from '@/components/ui/color-mode';
|
||||
@ -19,7 +19,6 @@ import {
|
||||
useSpecificType,
|
||||
useTypeSeriesSeasonGetVideo,
|
||||
} from '@/service';
|
||||
import { DisplayMediaListFull } from '@/components/media/DisplayMediaListFull';
|
||||
|
||||
export const TypesSeriesSeasonDetailPage = () => {
|
||||
const { typeId, seriesId, seasonId } = useParams();
|
||||
@ -107,8 +106,8 @@ export const TypesSeriesSeasonDetailPage = () => {
|
||||
>
|
||||
{videos?.map((data) => (
|
||||
<Box
|
||||
key={data.id}
|
||||
width="calc(max(300px,50%))"
|
||||
key={data.id}
|
||||
width={{sm:"95%", base:"calc(max(300px,50%))"}}
|
||||
height="60px"
|
||||
border="1px"
|
||||
borderColor="brand.900"
|
||||
|
@ -19,6 +19,7 @@ export type ActivePlaylistServiceProps = {
|
||||
previous: () => void;
|
||||
next: () => void;
|
||||
first: () => void;
|
||||
clear: () => void;
|
||||
};
|
||||
|
||||
export const useActivePlaylistService = (): ActivePlaylistServiceProps => {
|
||||
@ -140,5 +141,6 @@ export const useActivePlaylistServiceWrapped = (
|
||||
next,
|
||||
MediaActive,
|
||||
first,
|
||||
clear,
|
||||
};
|
||||
};
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user