develop #172

Merged
andrey.dekterev merged 9 commits from develop into master 3 years ago
  1. 22
      public/index.html
  2. 2
      src/config/procedures.tsx
  3. 12
      src/features/AddCardForm/components/Form/hooks/index.tsx
  4. 4
      src/features/AddCardForm/components/Form/hooks/useCountries.tsx
  5. 27
      src/features/AddCardForm/components/Form/index.tsx
  6. 6
      src/features/AddCardForm/styled.tsx
  7. 7
      src/features/Combobox/hooks/index.tsx
  8. 3
      src/features/Combobox/index.tsx
  9. 5
      src/features/Combobox/styled.tsx
  10. 1
      src/features/Combobox/types.tsx
  11. 1
      src/features/Common/Input/styled.tsx
  12. 7
      src/features/Landings/styled.tsx
  13. 24
      src/features/MatchPage/components/LiveMatch/hooks/index.tsx
  14. 37
      src/features/MatchPage/components/LiveMatch/hooks/useLastPlayPosition.tsx
  15. 32
      src/features/MatchPage/components/LiveMatch/hooks/usePlayerProgressReporter.tsx
  16. 23
      src/features/MatchPage/components/LiveMatch/hooks/useUrlParam.tsx
  17. 4
      src/features/MatchPage/components/MatchDescription/styled.tsx
  18. 5
      src/features/MatchPage/store/hooks/useTournamentData.tsx
  19. 22
      src/features/MatchPopup/components/LiveMatchPlaylist/index.tsx
  20. 1
      src/features/MatchPopup/components/LiveMatchPopup/styled.tsx
  21. 6
      src/features/MatchSidePlaylists/styled.tsx
  22. 1
      src/features/MultiSourcePlayer/components/ProgressBar/index.tsx
  23. 2
      src/features/StreamPlayer/components/Controls/Components/ControlsMobile/styled.tsx
  24. 4
      src/features/StreamPlayer/components/ProgressBar/index.tsx
  25. 29
      src/features/StreamPlayer/components/ProgressBar/styled.tsx
  26. 43
      src/features/StreamPlayer/hooks/index.tsx
  27. 43
      src/requests/getMatchLastWatchSeconds.tsx
  28. 2
      src/requests/index.tsx
  29. 38
      src/requests/reportPlayerProgress.tsx

@ -42,6 +42,13 @@
name="msapplication-config" name="msapplication-config"
content="%PUBLIC_URL%/clients/%REACT_APP_CLIENT%/favicon/browserconfig.xml" /> content="%PUBLIC_URL%/clients/%REACT_APP_CLIENT%/favicon/browserconfig.xml" />
<% } %> <% } %>
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-5K7GHJB');</script>
<!-- End Google Tag Manager -->
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
@ -59,18 +66,11 @@
<!-- Start of ChromeCast script --> <!-- Start of ChromeCast script -->
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script> <script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
<!-- End of ChromeCast script --> <!-- End of ChromeCast script -->
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LZXGB5GJG0"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LZXGB5GJG0');
</script>
<!-- Google Tag Manager (noscript) --> <!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5K7GHJB" <noscript>
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> <iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5K7GHJB"
height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) --> <!-- End Google Tag Manager (noscript) -->
</body> </body>
</html> </html>

@ -21,7 +21,6 @@ export const PROCEDURES = {
get_user_agreemens: 'get_user_agreemens', get_user_agreemens: 'get_user_agreemens',
get_user_favorites: 'get_user_favorites', get_user_favorites: 'get_user_favorites',
get_user_info: 'get_user_info', get_user_info: 'get_user_info',
get_user_match_second: 'get_user_match_second',
get_user_payments: 'get_user_payments', get_user_payments: 'get_user_payments',
get_user_preferences: 'get_user_preferences', get_user_preferences: 'get_user_preferences',
get_user_subscribes: 'get_user_subscribes', get_user_subscribes: 'get_user_subscribes',
@ -37,7 +36,6 @@ export const PROCEDURES = {
save_user_custom_subscription: 'save_user_custom_subscription', save_user_custom_subscription: 'save_user_custom_subscription',
save_user_favorite: 'save_user_favorite', save_user_favorite: 'save_user_favorite',
save_user_info: 'save_user_info', save_user_info: 'save_user_info',
save_user_match_second: 'save_user_match_second',
save_user_page: 'save_user_page', save_user_page: 'save_user_page',
save_user_preferences: 'save_user_preferences', save_user_preferences: 'save_user_preferences',
save_user_subscription: 'save_user_subscription', save_user_subscription: 'save_user_subscription',

@ -104,6 +104,8 @@ export const useFormSubmit = ({
const [inputStates, setInputStates] = useObjectState(initialState) const [inputStates, setInputStates] = useObjectState(initialState)
const [errorMessage, setErrorMessage] = useState('') const [errorMessage, setErrorMessage] = useState('')
const [loader, setLoader] = useState(false) const [loader, setLoader] = useState(false)
const [typedCountry, setTypedCountry] = useState<string>('')
const dataHighlights = useRecoilValue(dataForPayHighlights) const dataHighlights = useRecoilValue(dataForPayHighlights)
const { const {
@ -161,7 +163,7 @@ export const useFormSubmit = ({
} }
} }
const onCountryChange = useCallback((country: SelectedCountry) => { const onCountrySelect = useCallback((country: SelectedCountry) => {
resetErrors() resetErrors()
if (country?.id === selectedCountry?.id) return if (country?.id === selectedCountry?.id) return
setSelectedCountry(country) setSelectedCountry(country)
@ -178,6 +180,10 @@ export const useFormSubmit = ({
setSelectedState, setSelectedState,
]) ])
const onChangeCountry = (e: ChangeEvent<HTMLInputElement>) => {
setTypedCountry(e.target.value)
}
const onStateSelect = useCallback((state: SelectedState) => { const onStateSelect = useCallback((state: SelectedState) => {
resetErrors() resetErrors()
if (state?.id === selectedState?.id) return if (state?.id === selectedState?.id) return
@ -334,8 +340,9 @@ export const useFormSubmit = ({
loader, loader,
name, name,
onAddressChange, onAddressChange,
onChangeCountry,
onCityChange, onCityChange,
onCountryChange, onCountrySelect,
onInputsBlur, onInputsBlur,
onInputsChange, onInputsChange,
onInputsFocus, onInputsFocus,
@ -344,6 +351,7 @@ export const useFormSubmit = ({
onStateSelect, onStateSelect,
selectedCountry, selectedCountry,
selectedState, selectedState,
typedCountry,
usaStateValue, usaStateValue,
usaStates, usaStates,
} }

@ -19,6 +19,8 @@ export type SelectedState = (UsaStateType & {
name: string, name: string,
}) | null }) | null
export const USA_ID = 194
const useCountriesList = () => { const useCountriesList = () => {
const [countries, setCountries] = useState<Countries>([]) const [countries, setCountries] = useState<Countries>([])
@ -47,7 +49,7 @@ export const useCountries = () => {
) )
useEffect(() => { useEffect(() => {
if (selectedCountry?.id === 194) { if (selectedCountry?.id === USA_ID) {
(async () => { (async () => {
const states = await getUsaStates() const states = await getUsaStates()
setUsaStates(states) setUsaStates(states)

@ -1,8 +1,8 @@
import { useRouteMatch } from 'react-router-dom' import { useRouteMatch } from 'react-router-dom'
import { import {
CardNumberElement,
CardExpiryElement,
CardCvcElement, CardCvcElement,
CardExpiryElement,
CardNumberElement,
} from '@stripe/react-stripe-js' } from '@stripe/react-stripe-js'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
@ -16,7 +16,8 @@ import { SolidButton } from 'features/UserAccount/styled'
import { ElementContainer } from '../ElementContainer' import { ElementContainer } from '../ElementContainer'
import type { Props } from './hooks' import type { Props } from './hooks'
import { useFormSubmit, ElementTypes } from './hooks' import { ElementTypes, useFormSubmit } from './hooks'
import { USA_ID } from './hooks/useCountries'
import { import {
Form, Form,
@ -81,8 +82,9 @@ export const AddCardFormInner = (props: Props) => {
loader, loader,
name, name,
onAddressChange, onAddressChange,
onChangeCountry,
onCityChange, onCityChange,
onCountryChange, onCountrySelect,
onInputsBlur, onInputsBlur,
onInputsChange, onInputsChange,
onInputsFocus, onInputsFocus,
@ -91,11 +93,12 @@ export const AddCardFormInner = (props: Props) => {
onStateSelect, onStateSelect,
selectedCountry, selectedCountry,
selectedState, selectedState,
typedCountry,
usaStates, usaStates,
usaStateValue, usaStateValue,
} = useFormSubmit(props) } = useFormSubmit(props)
const isUsaCountry = selectedCountry?.id === 194 const isUsaCountry = selectedCountry?.id === USA_ID
return ( return (
<Form onSubmit={handleSubmit}> <Form onSubmit={handleSubmit}>
@ -166,14 +169,20 @@ export const AddCardFormInner = (props: Props) => {
<Column> <Column>
<CountryWrapper> <CountryWrapper>
<CustomCombobox <CustomCombobox
value={selectedCountry?.name || ''} value={selectedCountry?.name || typedCountry}
labelLexic={selectedCountry?.name ? '' : 'country'} labelLexic={
onSelect={onCountryChange} (selectedCountry?.name || typedCountry)
|| !isLabelVisible(ElementTypes.CardCountry)
? ''
: 'country'
}
onSelect={onCountrySelect}
onBlur={onInputsBlur(ElementTypes.CardCountry)} onBlur={onInputsBlur(ElementTypes.CardCountry)}
options={countries} options={countries}
withError={false} withError={false}
selected={Boolean(selectedCountry?.name)} selected={Boolean(selectedCountry?.name)}
noSearch onChange={onChangeCountry}
onFocus={onInputsFocus(ElementTypes.CardCountry)}
/> />
</CountryWrapper> </CountryWrapper>
{isUsaCountry && ( {isUsaCountry && (

@ -133,5 +133,11 @@ export const CustomCombobox = styled(Combobox)`
${PopOver}{ ${PopOver}{
top: 55px; top: 55px;
max-height: 300px; max-height: 300px;
${isMobileDevice
? css`
top: 30px;
`
: ''};
} }
` as typeof Combobox ` as typeof Combobox

@ -32,6 +32,7 @@ export const useCombobox = <T extends Option>({
noSearch, noSearch,
onBlur, onBlur,
onChange, onChange,
onFocus,
onSelect, onSelect,
options, options,
selected, selected,
@ -104,6 +105,11 @@ export const useCombobox = <T extends Option>({
onBlur?.(event) onBlur?.(event)
} }
const onInputFocus = (event: FocusEvent<HTMLInputElement>) => {
open()
onFocus?.(event)
}
useEffect(() => { useEffect(() => {
const selectedIndex = findIndex(filteredOptions, ({ name }) => name === query) const selectedIndex = findIndex(filteredOptions, ({ name }) => name === query)
setIndex(selectedIndex) setIndex(selectedIndex)
@ -138,6 +144,7 @@ export const useCombobox = <T extends Option>({
inputFieldRef, inputFieldRef,
isOpen, isOpen,
onInputBlur, onInputBlur,
onInputFocus,
onKeyDown, onKeyDown,
onOptionSelect, onOptionSelect,
onOutsideClick, onOutsideClick,

@ -61,6 +61,7 @@ export const Combobox = <T extends Option>(props: Props<T>) => {
inputFieldRef, inputFieldRef,
isOpen, isOpen,
onInputBlur, onInputBlur,
onInputFocus,
onKeyDown, onKeyDown,
onOptionSelect, onOptionSelect,
onOutsideClick, onOutsideClick,
@ -102,7 +103,7 @@ export const Combobox = <T extends Option>(props: Props<T>) => {
<InputStyled <InputStyled
maxLength={maxLength} maxLength={maxLength}
onBlur={onInputBlur} onBlur={onInputBlur}
onFocus={open} onFocus={onInputFocus || open}
ref={inputFieldRef} ref={inputFieldRef}
autoComplete='off' autoComplete='off'
title={title} title={title}

@ -36,7 +36,6 @@ export const ListOption = styled.li.attrs(() => ({
padding-left: 24px; padding-left: 24px;
color: ${({ theme }) => theme.colors.white70}; color: ${({ theme }) => theme.colors.white70};
background-color: transparent; background-color: transparent;
text-transform: capitalize;
${({ isHighlighted }) => isHighlighted && css` ${({ isHighlighted }) => isHighlighted && css`
color: ${({ theme }) => theme.colors.white}; color: ${({ theme }) => theme.colors.white};
@ -74,10 +73,6 @@ export const WrapperIcon = styled.span`
: ''}; : ''};
` `
export const ScAudioWrap = styled.div`
margin-left: 20px;
`
export const ScLoaderWrapper = styled.div` export const ScLoaderWrapper = styled.div`
border-radius: 10px; border-radius: 10px;
display: flex; display: flex;

@ -18,6 +18,7 @@ export type Props<T> = Pick<InputHTMLAttributes<HTMLInputElement>, (
| 'title' | 'title'
| 'placeholder' | 'placeholder'
| 'onBlur' | 'onBlur'
| 'onFocus'
)> & { )> & {
className?: string, className?: string,
customListStyles?: CustomStyles, customListStyles?: CustomStyles,

@ -129,7 +129,6 @@ const inputStyles = css<InputProps>`
export const InputStyled = styled.input<InputProps>` export const InputStyled = styled.input<InputProps>`
${inputStyles} ${inputStyles}
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)}; opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
text-transform: capitalize;
::placeholder { ::placeholder {
opacity: 0; opacity: 0;

@ -182,6 +182,7 @@ export const SliderImg = styled.img<SliderImgProps>`
100% { 100% {
transform: scale(1.1); transform: scale(1.1);
} }
}
` `
export const SliderSwitch = styled.div` export const SliderSwitch = styled.div`
@ -276,9 +277,10 @@ export const TournamentTitle = styled.div`
` `
export const TournamentButton = styled(ButtonSolid)<ButtonProps>` export const TournamentButton = styled(ButtonSolid)<ButtonProps>`
width: 320px; min-width: 320px;
width: fit-content;
height: fit-content; height: fit-content;
font-size: 1.13rem;; font-size: 1.13rem;
font-weight: 600; font-weight: 600;
border-radius: 5px; border-radius: 5px;
margin-bottom: 4.25rem; margin-bottom: 4.25rem;
@ -290,6 +292,7 @@ export const TournamentButton = styled(ButtonSolid)<ButtonProps>`
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 100%; width: 100%;
min-width: 100%;
font-size: 17px; font-size: 17px;
padding: 10px 0; padding: 10px 0;
margin-bottom: 0; margin-bottom: 0;

@ -4,11 +4,11 @@ import { API_ROOT } from 'config'
import { readToken } from 'helpers/token' import { readToken } from 'helpers/token'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
import { usePlayerProgressReporter } from './usePlayerProgressReporter' import { MatchSecondType, usePlayerProgressReporter } from './usePlayerProgressReporter'
import { useResumeUrlParam } from './useResumeUrlParam' import { useResumeUrlParam } from './useResumeUrlParam'
import { useChapters } from './useChapters' import { useChapters } from './useChapters'
import { usePlaylistLogger } from './usePlaylistLogger' import { usePlaylistLogger } from './usePlaylistLogger'
@ -21,15 +21,17 @@ export const useLiveMatch = () => {
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
} = useMatchPageStore() } = useMatchPageStore()
const { profileId: matchId, sportType } = usePageParams() const { profileId: matchId, sportType } = usePageParams()
const resume = useResumeUrlParam() const resumeFromParam = useResumeUrlParam()
const fromStartIfStreamPaused = useMemo( const resumeFromLocalStorage = useMemo(() => {
() => (profile && !profile.live ? 0 : undefined), const lastSecondLS = localStorage.getItem('matchLastWatchSecond')
// deps намеренно оставляем пустым, const matchesLastWatchSecond: MatchSecondType | null = lastSecondLS && JSON.parse(lastSecondLS)
// не нужно реагировать на изменение live когда пользователь смотрит матч // undefined означает, что юзер будет смотреть лайв
// eslint-disable-next-line react-hooks/exhaustive-deps return profile && !profile.live
[], ? matchesLastWatchSecond?.[sportType]?.[matchId]?.lastWatchSecond || 0
) : undefined
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [profile])
const { chapters } = useChapters({ const { chapters } = useChapters({
profile, profile,
@ -69,7 +71,7 @@ export const useLiveMatch = () => {
onPlayerProgressChange, onPlayerProgressChange,
onPlayingChange, onPlayingChange,
onPlaylistSelect, onPlaylistSelect,
resume: resume ?? fromStartIfStreamPaused, resume: resumeFromParam ?? resumeFromLocalStorage,
streamUrl: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8`, streamUrl: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8`,
} }
} }

@ -1,37 +0,0 @@
import { useEffect, useState } from 'react'
import type { LastPlayPosition } from 'requests'
import { getMatchLastWatchSeconds } from 'requests'
import { usePageParams } from 'hooks/usePageParams'
import { useRequest } from 'hooks/useRequest'
const initialPosition = {
half: 0,
second: 0,
}
export const useLastPlayPosition = () => {
const { profileId: matchId, sportType } = usePageParams()
const [
lastPlayPosition,
setPosition,
] = useState<LastPlayPosition>(initialPosition)
const {
isFetching: isLastPlayPositionFetching,
request: requestLastPlayPosition,
} = useRequest(getMatchLastWatchSeconds)
useEffect(() => {
requestLastPlayPosition(sportType, matchId).then(setPosition)
}, [
sportType,
matchId,
requestLastPlayPosition,
])
return {
isLastPlayPositionFetching,
lastPlayPosition,
}
}

@ -1,25 +1,41 @@
import { useCallback, useRef } from 'react' import { useCallback, useRef } from 'react'
import { reportPlayerProgress } from 'requests'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams } from 'hooks/usePageParams'
import { useInterval } from 'hooks/useInterval' import { useInterval } from 'hooks/useInterval'
const reportRequestInterval = 30000 const reportRequestInterval = 30000
export type MatchSecondType = {
[sportType: number]: {
[matchId: number]: {
lastWatchSecond: number,
period: number,
},
},
}
export const usePlayerProgressReporter = () => { export const usePlayerProgressReporter = () => {
const { profileId: matchId, sportType } = usePageParams() const { profileId: matchId, sportType } = usePageParams()
const playerData = useRef({ period: 0, seconds: 0 }) const playerData = useRef({ period: 0, seconds: 0 })
const intervalCallback = () => { const intervalCallback = () => {
const { period, seconds } = playerData.current const { period, seconds } = playerData.current
reportPlayerProgress({
half: period, const matchSecond = localStorage.getItem('matchLastWatchSecond')
matchId, const matchSecondParsed: MatchSecondType = matchSecond ? JSON.parse(matchSecond) : {}
seconds,
sport: sportType, localStorage.setItem('matchLastWatchSecond', JSON.stringify({
}) ...matchSecondParsed,
[sportType]: {
...matchSecondParsed?.[sportType],
[matchId]: {
lastWatchSecond: seconds,
period,
},
},
}))
} }
const { start, stop } = useInterval({ const { start, stop } = useInterval({
callback: intervalCallback, callback: intervalCallback,
intervalDuration: reportRequestInterval, intervalDuration: reportRequestInterval,

@ -1,23 +0,0 @@
import { useMemo } from 'react'
import { useLocation } from 'react-router'
import isNumber from 'lodash/isNumber'
export const RESUME_KEY = 'resume'
const readResumeParam = (search: string) => {
const params = new URLSearchParams(search)
const rawValue = params.get(RESUME_KEY)
if (!rawValue) return undefined
const value = JSON.parse(rawValue)
return isNumber(value) ? value : 0
}
export const useUrlParam = () => {
const { search } = useLocation()
const resume = useMemo(() => readResumeParam(search), [search])
return resume
}

@ -11,7 +11,7 @@ export const Description = styled.div<{isHidden?: boolean}>`
${isMobileDevice ${isMobileDevice
? css` ? css`
padding: 0 5px; padding: 0 15px;
align-items: baseline; align-items: baseline;
` `
: ''}; : ''};
@ -24,8 +24,6 @@ export const Description = styled.div<{isHidden?: boolean}>`
` `
export const DescriptionInnerBlock = styled.div` export const DescriptionInnerBlock = styled.div`
margin-right: 10px;
${isMobileDevice ? css` ${isMobileDevice ? css`
width: 100%; width: 100%;
` : ''} ` : ''}

@ -11,6 +11,7 @@ import sortBy from 'lodash/sortBy'
import type { Match } from 'features/Matches' import type { Match } from 'features/Matches'
import { prepareMatches } from 'features/Matches/helpers/prepareMatches' import { prepareMatches } from 'features/Matches/helpers/prepareMatches'
import { useAuthStore } from 'features/AuthStore'
import type { MatchInfo } from 'requests' import type { MatchInfo } from 'requests'
import { getTournamentMatches } from 'requests' import { getTournamentMatches } from 'requests'
@ -23,6 +24,7 @@ import { TournamentData } from '../../types'
export const useTournamentData = (matchProfile: MatchInfo) => { export const useTournamentData = (matchProfile: MatchInfo) => {
const { sportType } = usePageParams() const { sportType } = usePageParams()
const { user } = useAuthStore()
const [tournamentMatches, setTournamentMatches] = useState<Array<Match>>([]) const [tournamentMatches, setTournamentMatches] = useState<Array<Match>>([])
const [matchDates, setMatchDates] = useState<Array<string>>([]) const [matchDates, setMatchDates] = useState<Array<string>>([])
@ -44,7 +46,7 @@ export const useTournamentData = (matchProfile: MatchInfo) => {
)).sort((a, b) => a.getTime() - b.getTime()) )).sort((a, b) => a.getTime() - b.getTime())
setMatchDates(sortedUniq(matchDateList.map((date) => format(date, 'yyyy-MM-dd')))) setMatchDates(sortedUniq(matchDateList.map((date) => format(date, 'yyyy-MM-dd'))))
setTournamentMatches(sortBy(prepareMatches(matchesBySection.broadcast), ['date'])) setTournamentMatches(sortBy(prepareMatches(matchesBySection.broadcast, user), ['date']))
})() })()
} }
}, [ }, [
@ -52,6 +54,7 @@ export const useTournamentData = (matchProfile: MatchInfo) => {
sportType, sportType,
matchProfile?.live, matchProfile?.live,
matchProfile?.c_match_calc_status, matchProfile?.c_match_calc_status,
user,
]) ])
const tournamentData: TournamentData = useMemo(() => ({ const tournamentData: TournamentData = useMemo(() => ({

@ -3,22 +3,38 @@ import { useState, useEffect } from 'react'
import { PAGES } from 'config' import { PAGES } from 'config'
import { isMobileDevice } from 'config/userAgent' import { isMobileDevice } from 'config/userAgent'
import { getSportLexic } from 'helpers' import { getSportLexic } from 'helpers'
import { getMatchLastWatchSeconds, LastPlayPosition } from 'requests'
import { useMatchPopupStore } from 'features/MatchPopup/store' import { useMatchPopupStore } from 'features/MatchPopup/store'
import type { MatchSecondType } from 'features/MatchPage/components/LiveMatch/hooks/usePlayerProgressReporter'
import { useLocalStore } from 'hooks'
import { SimplePlaylistButton } from '../SimplePlaylistButton' import { SimplePlaylistButton } from '../SimplePlaylistButton'
import { List, Item } from './styled' import { List, Item } from './styled'
type LastPlayPosition = {
half: number,
second: number,
}
export const LiveMatchPlaylist = () => { export const LiveMatchPlaylist = () => {
const [lastPlayPosition, setLastPlayPosition] = useState<LastPlayPosition | null>(null) const [lastPlayPosition, setLastPlayPosition] = useState<LastPlayPosition | null>(null)
const { match } = useMatchPopupStore() const { match } = useMatchPopupStore()
const [lastWatchSecond] = useLocalStore<MatchSecondType>({
key: 'matchLastWatchSecond',
})
useEffect(() => { useEffect(() => {
if (match) { if (match) {
getMatchLastWatchSeconds(match?.sportType, match?.id) const matchTime = lastWatchSecond?.[match.sportType]?.[match.id]
.then((lastPlayPositionSecond) => setLastPlayPosition(lastPlayPositionSecond))
setLastPlayPosition({
half: matchTime?.period || 0,
second: matchTime?.lastWatchSecond || 0,
})
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [match]) }, [match])
if (!match) return null if (!match) return null

@ -11,7 +11,6 @@ export const Modal = styled(BaseModal)`
${ModalWindow} { ${ModalWindow} {
width: 27.22rem; width: 27.22rem;
min-height: 14.859rem;
padding: 1.416rem 0.71rem; padding: 1.416rem 0.71rem;
border-radius: 5px; border-radius: 5px;

@ -30,9 +30,11 @@ export const Wrapper = styled.div<WrapperProps>`
? css` ? css`
overflow-y: auto; overflow-y: auto;
width: 100%; width: 100%;
padding-right: 0; padding: 10px 10px 0;
${customScrollbar} ::-webkit-scrollbar {
display: none;
}
` `
: ''}; : ''};

@ -41,7 +41,6 @@ export const ProgressBar = (props: ProgressBarProps) => {
{isScrubberVisible && ( {isScrubberVisible && (
<ScrubberContainer> <ScrubberContainer>
<Scrubber <Scrubber
isIOS={isIOS}
style={{ left: `${playedProgressInPercent}%` }} style={{ left: `${playedProgressInPercent}%` }}
> >
<TimeTooltip time={time} /> <TimeTooltip time={time} />

@ -15,7 +15,7 @@ export const Controls = styled.div<FullscreenProps>`
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25)); filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
bottom: ${({ isFullscreen }) => (isFullscreen ? '20px' : 0)}; bottom: ${({ isFullscreen }) => (isFullscreen ? '20px' : '-3px')};
@media (orientation: landscape){ @media (orientation: landscape){
width: 100%; width: 100%;

@ -2,8 +2,6 @@ import { useSlider } from 'features/StreamPlayer/hooks/useSlider'
import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip' import { TimeTooltip } from 'features/StreamPlayer/components/TimeTooltip'
import { Scrubber, ScrubberContainer } from 'features/StreamPlayer/components/ProgressBar/styled' import { Scrubber, ScrubberContainer } from 'features/StreamPlayer/components/ProgressBar/styled'
import { isIOS } from 'config/userAgent'
import { Chapters } from '../Chapters' import { Chapters } from '../Chapters'
import type { Props } from './hooks' import type { Props } from './hooks'
import { useProgressBar } from './hooks' import { useProgressBar } from './hooks'
@ -20,13 +18,11 @@ export const ProgressBar = (props: Props) => {
return ( return (
<ProgressBarList <ProgressBarList
ref={progressBarRef} ref={progressBarRef}
isIOS={isIOS}
> >
<Chapters chapters={calculatedChapters} /> <Chapters chapters={calculatedChapters} />
{isScrubberVisible && ( {isScrubberVisible && (
<ScrubberContainer> <ScrubberContainer>
<Scrubber <Scrubber
isIOS={isIOS}
style={{ left: `${playedProgressInPercent}%` }} style={{ left: `${playedProgressInPercent}%` }}
> >
<TimeTooltip time={time} /> <TimeTooltip time={time} />

@ -4,7 +4,7 @@ import { isMobileDevice } from 'config/userAgent'
import { Wrapper } from '../TimeTooltip/styled' import { Wrapper } from '../TimeTooltip/styled'
export const ProgressBarList = styled.div<{isIOS?: boolean}>` export const ProgressBarList = styled.div`
flex-grow: 1; flex-grow: 1;
height: 4px; height: 4px;
position: relative; position: relative;
@ -13,16 +13,12 @@ export const ProgressBarList = styled.div<{isIOS?: boolean}>`
${isMobileDevice ${isMobileDevice
? css` ? css`
height: 1px; height: 3px;
margin: 0 15px;
` `
: ''} : ''}
${({ isIOS }) => (isIOS && isMobileDevice
&& css`
height: 3px;
margin: 0 24px;
`)}
` `
export const ScrubberContainer = styled.div` export const ScrubberContainer = styled.div`
${isMobileDevice ${isMobileDevice
? css` ? css`
@ -30,10 +26,9 @@ export const ScrubberContainer = styled.div`
margin: 0 7px; margin: 0 7px;
` `
: ''} : ''}
` `
export const Scrubber = styled.div<{isIOS?: boolean}>` export const Scrubber = styled.div`
border: none; border: none;
outline: none; outline: none;
position: absolute; position: absolute;
@ -54,18 +49,10 @@ export const Scrubber = styled.div<{isIOS?: boolean}>`
${isMobileDevice ${isMobileDevice
? css` ? css`
width: 14px;
height: 14px;
background-color: #FFFFFF; background-color: #FFFFFF;
transform: translate(-50%, -48%); width: 27px;
` height: 27px;
: ''}
${({ isIOS }) => (isIOS && isMobileDevice
? css`
width: 30px;
height: 30px;
transform: translate(-50%, -53%); transform: translate(-50%, -53%);
` `
: '')} : ''}
` `

@ -1,5 +1,6 @@
import type { MouseEvent } from 'react'
import { import {
MouseEvent,
useMemo,
useRef, useRef,
useCallback, useCallback,
useEffect, useEffect,
@ -11,6 +12,7 @@ import { useTour } from '@reactour/tour'
import size from 'lodash/size' import size from 'lodash/size'
import isNumber from 'lodash/isNumber' import isNumber from 'lodash/isNumber'
import isEmpty from 'lodash/isEmpty' import isEmpty from 'lodash/isEmpty'
import isUndefined from 'lodash/isUndefined'
import Hls from 'hls.js' import Hls from 'hls.js'
@ -111,14 +113,23 @@ export const useVideoPlayer = ({
/** время для сохранения статистики просмотра матча */ /** время для сохранения статистики просмотра матча */
const timeForStatistics = useRef(0) const timeForStatistics = useRef(0)
const resumeTimeWithOffset = useMemo(() => {
const chapterWithOffset = chapters[0].startOffsetMs / 1000
return !isUndefined(resumeFrom) && isLive && chapters[0].isFullMatchChapter
? resumeFrom + chapterWithOffset
: resumeFrom
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [resumeFrom])
const { url } = chapters[0] ?? { url: '' } const { url } = chapters[0] ?? { url: '' }
const numberOfChapters = size(chapters) const numberOfChapters = size(chapters)
const { hls, videoRef } = useHlsPlayer({ const { hls, videoRef } = useHlsPlayer({
isLive, isLive,
resumeFrom, resumeFrom: resumeTimeWithOffset,
src: url, src: url,
}) })
const [isLivePlaying, setIsLivePlaying] = useState(false) // временно закоментил, если ничего не сломается, удалю
// const [isLivePlaying, setIsLivePlaying] = useState(false)
const [isPausedTime, setIsPausedTime] = useState(false) const [isPausedTime, setIsPausedTime] = useState(false)
const [pausedProgress, setPausedProgress] = useState(0) const [pausedProgress, setPausedProgress] = useState(0)
@ -253,12 +264,12 @@ export const useVideoPlayer = ({
if (selectedPlaylist?.id !== FULL_GAME_KEY) { if (selectedPlaylist?.id !== FULL_GAME_KEY) {
restartVideo() restartVideo()
setIsLivePlaying(true) // setIsLivePlaying(true)
} }
const liveProgressMs = Math.max(fullMatchDuration - BUFFERING_TIME, 0) const liveProgressMs = Math.max(fullMatchDuration - BUFFERING_TIME, 0)
setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 }) setPlayerState({ playedProgress: liveProgressMs, seek: liveProgressMs / 1000 })
if (liveProgressMs > 0) setIsLivePlaying(false) // if (liveProgressMs > 0) setIsLivePlaying(false)
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
duration, duration,
@ -296,11 +307,11 @@ export const useVideoPlayer = ({
}, [selectedPlaylist]) }, [selectedPlaylist])
useEffect(() => { useEffect(() => {
if (duration && isLivePlaying && chapters[0]?.isFullMatchChapter) { if (duration && isUndefined(resumeFrom) && chaptersProps[0]?.isFullMatchChapter) {
backToLive() backToLive()
} }
// eslint-disable-next-line // eslint-disable-next-line
}, [duration, isLivePlaying]) }, [])
useEffect(() => { useEffect(() => {
if (duration if (duration
@ -328,12 +339,16 @@ export const useVideoPlayer = ({
}, [seek, setPlayerState]) }, [seek, setPlayerState])
useEffect(() => { useEffect(() => {
onPlayingChange(playing) onPlayingChange(selectedPlaylist?.id === FULL_GAME_KEY ? playing : false)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [playing, selectedPlaylist])
useEffect(() => {
if (playing) { if (playing) {
setPlayerState({ buffering: false }) setPlayerState({ buffering: false })
} }
// eslint-disable-next-line // eslint-disable-next-line
}, [playing, onPlayingChange]) }, [playing])
const regURL = /\d{6,20}/gi const regURL = /\d{6,20}/gi
@ -353,6 +368,16 @@ export const useVideoPlayer = ({
&& chapters[0]?.url.match(regURL)?.[0] === chaptersProps[0]?.url.match(regURL)?.[0]) && chapters[0]?.url.match(regURL)?.[0] === chaptersProps[0]?.url.match(regURL)?.[0])
|| (isEmpty(chapters) || isEmpty(chaptersProps))) return || (isEmpty(chapters) || isEmpty(chaptersProps))) return
if (!isUndefined(resumeFrom) && chaptersProps[0].isFullMatchChapter) {
setPlayerState({
...initialState,
chapters: chaptersProps,
playing: true,
seek: resumeFrom + chaptersProps[0].startOffsetMs / 1000,
})
return
}
setPlayerState({ setPlayerState({
...initialState, ...initialState,
chapters: chaptersProps, chapters: chaptersProps,

@ -1,43 +0,0 @@
import {
DATA_URL,
PROCEDURES,
} from 'config'
import { callApi } from 'helpers'
const proc = PROCEDURES.get_user_match_second
type Response = {
_p_half: number | null,
_p_second: number | null,
}
export type LastPlayPosition = {
half: number,
second: number,
}
export const getMatchLastWatchSeconds = async (
sportType: number,
matchId: number,
) => {
const config = {
body: {
params: {
_p_match_id: matchId,
_p_sport: sportType,
},
proc,
},
}
const response: Response = await callApi({
config,
url: DATA_URL,
})
return {
half: response?._p_half ?? 0,
second: response?._p_second ?? 0,
}
}

@ -14,10 +14,8 @@ export * from './getUserInfo'
export * from './getMatchInfo' export * from './getMatchInfo'
export * from './getVideos' export * from './getVideos'
export * from './getUnauthenticatedMatch' export * from './getUnauthenticatedMatch'
export * from './reportPlayerProgress'
export * from './saveUserInfo' export * from './saveUserInfo'
export * from './getPlayerInfo' export * from './getPlayerInfo'
export * from './getMatchLastWatchSeconds'
export * from './getMatchesPreviewImages' export * from './getMatchesPreviewImages'
export * from './getSportActions' export * from './getSportActions'
export * from './getMatchEvents' export * from './getMatchEvents'

@ -1,38 +0,0 @@
import {
DATA_URL,
PROCEDURES,
} from 'config'
import { callApi } from 'helpers'
const proc = PROCEDURES.save_user_match_second
type Args = {
half?: number,
matchId: number,
seconds: number,
sport: number,
}
export const reportPlayerProgress = ({
half,
matchId,
seconds,
sport,
}: Args) => {
const config = {
body: {
params: {
_p_half: half,
_p_match_id: matchId,
_p_second: seconds,
_p_sport: sport,
},
proc,
},
}
callApi({
config,
url: DATA_URL,
})
}
Loading…
Cancel
Save