[FEAT] update fullscreen management

This commit is contained in:
Edouard DUPIN 2025-02-25 00:12:30 +01:00
parent ae9f00a60a
commit 57a48de52f
23 changed files with 397 additions and 261 deletions

4
.gitignore vendored
View File

@ -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

View File

@ -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
View 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}
```

View File

@ -1,2 +1,2 @@
# URL for database connection
VITE_API_BASE_URL=karusic/api/
VITE_API_BASE_URL=karideo/api/

View File

@ -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>
);
};

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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}
/>
);
}

View File

@ -5,7 +5,7 @@ export const EmptyEnd = () => {
<Box
width="full"
height="25%"
minHeight="250px"
minHeight="calc(max(50vh,500px))"
// borderWidth="1px"
// borderColor="red"
></Box>

View File

@ -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}
/>
</>
);
};

View File

@ -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"

View File

@ -1,5 +1,6 @@
export * from './AudioPlayer';
export * from './Cover';
export * from './EmptyEnd';
export * from './Icon';
export * from './SearchInput';
export * from './VideoPlayer';

View File

@ -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"

View File

@ -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,
};
/**

View File

@ -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 />} />
)}

View File

@ -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 />

View File

@ -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>

View File

@ -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')}

View File

@ -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')}

View File

@ -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"

View File

@ -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