Skip to content

Add favorites and audio mode #461

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
- Localization—[see info on contributing](./packages/metastream-app/src/locale#contributing).

#### Are you a website owner?

Easily add watch party support to your website by redirecting the user to Metastream.

```html
<a href="https://app.getmetastream.com/?url=https://youtu.be/3bNITQR4Uso">Watch in Metastream</a>
```
Expand All @@ -41,10 +43,10 @@ Easily add watch party support to your website by redirecting the user to Metast
- [x] Add localization ([#5](https://github.com/samuelmaddock/metastream/issues/5))
- [x] Improve networking reliability ([#74](https://github.com/samuelmaddock/metastream/issues/74))
- [x] Port Metastream from Electron to a web app ([#94](https://github.com/samuelmaddock/metastream/issues/94))
- [ ] Improve UX and stability
- [ ] Add favorites/bookmarks ([#21](https://github.com/samuelmaddock/metastream/issues/21))
- [x] Improve UX and stability
- [x] Add favorites/bookmarks ([#21](https://github.com/samuelmaddock/metastream/issues/21))
- [ ] Add playlists
- [ ] Add audio mode ([#22](https://github.com/samuelmaddock/metastream/issues/22))
- [x] Add audio mode ([#22](https://github.com/samuelmaddock/metastream/issues/22))

Have a feature in mind? Make a request by [creating a GitHub issue](https://github.com/samuelmaddock/metastream/issues).

Expand Down
50 changes: 50 additions & 0 deletions packages/metastream-app/src/components/lobby/FavoritesList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react'
import { connect } from 'react-redux'
import { ListOverlay } from './ListOverlay'
import { IMediaItem } from '../../lobby/reducers/mediaPlayer'
import { IAppState } from '../../reducers'
import { removeFavorite } from '../../reducers/favorites'
import { MediaItem } from '../media/MediaItem'
import MenuItem from '@material-ui/core/MenuItem'
import { WithNamespaces, withNamespaces } from 'react-i18next'

interface ConnectedProps {
favorites: IMediaItem[]
}

interface DispatchProps {
removeFavorite(id: string): void
}

type Props = ConnectedProps & DispatchProps & WithNamespaces

const _FavoritesList: React.SFC<Props> = ({ favorites, t, removeFavorite }) => {
return (
<ListOverlay
id="favorites"
title={t('favorites')}
tagline={favorites.length ? `${favorites.length}` : undefined}
renderMenuOptions={(item, close) => (
<MenuItem
onClick={() => {
removeFavorite(item.id)
close()
}}
>
{t('remove')}
</MenuItem>
)}
>
{favorites.map((media) => (
<MediaItem key={media.id} media={media} onClickMenu={() => {}} />
))}
</ListOverlay>
)
}

export const FavoritesList = withNamespaces()(
connect<ConnectedProps, DispatchProps, {}, IAppState>(
(state) => ({ favorites: state.favorites.items }),
(dispatch) => ({ removeFavorite: (id) => dispatch(removeFavorite(id)) }),
)(_FavoritesList),
)
55 changes: 33 additions & 22 deletions packages/metastream-app/src/components/lobby/MediaList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { IMediaItem } from '../../lobby/reducers/mediaPlayer'
import {
getCurrentMedia,
getMediaQueue,
hasPlaybackPermissions
hasPlaybackPermissions,
} from '../../lobby/reducers/mediaPlayer.helpers'
import {
server_requestDeleteMedia,
server_requestMoveToTop,
server_requestToggleQueueLock
server_requestToggleQueueLock,
} from '../../lobby/actions/mediaPlayer'

import { IconButton } from '../common/button'
Expand All @@ -25,6 +25,7 @@ import { copyMediaLink, openMediaInBrowser } from '../../media/utils'
import { withNamespaces, WithNamespaces } from 'react-i18next'
import { sendMediaRequest } from 'lobby/actions/media-request'
import { setSetting } from 'actions/settings'
import { addFavorite } from 'reducers/favorites'

interface IProps {
className?: string
Expand All @@ -47,6 +48,7 @@ interface DispatchProps {
deleteMedia(mediaId: string): void
toggleQueueLock(): void
toggleCollapsed(): void
addFavorite(media: IMediaItem): void
}

type Props = IProps & IConnectedProps & DispatchProps & WithNamespaces
Expand Down Expand Up @@ -79,21 +81,21 @@ class _MediaList extends Component<Props> {
let items = [
{
label: t('openInBrowser'),
onClick: () => openMediaInBrowser(media)
onClick: () => openMediaInBrowser(media),
},
{
label: t('copyLink'),
onClick: () => copyMediaLink(media)
}
onClick: () => copyMediaLink(media),
},
]

if (media.description) {
items = [
...items,
{
label: t('info'),
onClick: () => this.props.onShowInfo(media)
}
onClick: () => this.props.onShowInfo(media),
},
]
}

Expand All @@ -102,16 +104,20 @@ class _MediaList extends Component<Props> {
...items,
{
label: t('moveToTop'),
onClick: () => this.props.moveToTop(media.id)
onClick: () => this.props.moveToTop(media.id),
},
{
label: t('addFavorite'),
onClick: () => this.props.addFavorite(media),
},
{
label: t('duplicate'),
onClick: () => this.props.sendMediaRequest(media.requestUrl)
onClick: () => this.props.sendMediaRequest(media.requestUrl),
},
{
label: t('remove'),
onClick: () => this.props.deleteMedia(media.id)
}
onClick: () => this.props.deleteMedia(media.id),
},
]
}

Expand All @@ -131,12 +137,12 @@ class _MediaList extends Component<Props> {
>
{this.props.collapsible && this.props.collapsed
? null
: this.mediaList.map(media => {
: this.mediaList.map((media) => {
return (
<MediaItem
key={media.id}
media={media}
onClickMenu={e => {
onClickMenu={(e) => {
this.listOverlay!.onSelect(e, media)
}}
/>
Expand Down Expand Up @@ -186,17 +192,19 @@ export const MediaList = withNamespaces()(
currentMedia: getCurrentMedia(state),
mediaQueue: getMediaQueue(state),
mediaQueueLocked: state.mediaPlayer.queueLocked,
collapsed: !!state.settings.mediaListCollapsed
collapsed: !!state.settings.mediaListCollapsed,
}),
(dispatch): DispatchProps => ({
moveToTop(mediaId) {
dispatch(server_requestMoveToTop(mediaId) as any)
},
sendMediaRequest(url) {
dispatch(sendMediaRequest({
url,
source: 'media-context-menu-duplicate'
}) as any)
dispatch(
sendMediaRequest({
url,
source: 'media-context-menu-duplicate',
}) as any,
)
},
deleteMedia(mediaId: string) {
dispatch(server_requestDeleteMedia(mediaId) as any)
Expand All @@ -205,8 +213,11 @@ export const MediaList = withNamespaces()(
dispatch(server_requestToggleQueueLock() as any)
},
toggleCollapsed() {
dispatch(setSetting('mediaListCollapsed', collapsed => !collapsed))
}
})
)(_MediaList)
dispatch(setSetting('mediaListCollapsed', (collapsed) => !collapsed))
},
addFavorite(media) {
dispatch(addFavorite(media))
},
}),
)(_MediaList),
)
4 changes: 4 additions & 0 deletions packages/metastream-app/src/components/lobby/VideoPlayer.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
background: #000;
}

.audioOnly {
visibility: hidden;
}

.interactTrigger {
composes: absolute-full from '~styles/layout.css';
z-index: 2;
Expand Down
19 changes: 10 additions & 9 deletions packages/metastream-app/src/components/lobby/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
updatePlaybackTimer,
server_requestSeek,
server_requestPlayPause,
server_requestSetPlaybackRate
server_requestSetPlaybackRate,
} from 'lobby/actions/mediaPlayer'
import { clamp } from 'utils/math'
import { MEDIA_REFERRER, MEDIA_SESSION_USER_AGENT } from 'constants/http'
Expand Down Expand Up @@ -89,7 +89,7 @@ const mapStateToProps = (state: IAppState): IConnectedProps => {
isExtensionInstalled: state.ui.isExtensionInstalled,
playerSettings: getPlayerSettings(state),
safeBrowseEnabled: state.settings.safeBrowse,
popupPlayer: state.ui.popupPlayer
popupPlayer: state.ui.popupPlayer,
}
}

Expand Down Expand Up @@ -279,7 +279,7 @@ class _VideoPlayer extends PureComponent<PrivateProps, IState> {
this.webview.dispatchRemoteEvent(
'metastream-host-event',
{ type, payload },
{ allFrames: true }
{ allFrames: true },
)
}
}
Expand Down Expand Up @@ -337,7 +337,7 @@ class _VideoPlayer extends PureComponent<PrivateProps, IState> {
}
},
200,
{ leading: true, trailing: true }
{ leading: true, trailing: true },
)

private onMediaSeek = throttle(
Expand All @@ -350,7 +350,7 @@ class _VideoPlayer extends PureComponent<PrivateProps, IState> {
}
},
500,
{ leading: true, trailing: true }
{ leading: true, trailing: true },
)

private onMediaVolumeChange = debounce((volume: number) => {
Expand All @@ -365,7 +365,7 @@ class _VideoPlayer extends PureComponent<PrivateProps, IState> {
this.props.dispatch(server_requestSetPlaybackRate(playbackRate))
},
200,
{ leading: true, trailing: true }
{ leading: true, trailing: true },
)

private onMediaReady = (isTopSubFrame: boolean = false, payload?: MediaReadyPayload) => {
Expand Down Expand Up @@ -400,7 +400,7 @@ class _VideoPlayer extends PureComponent<PrivateProps, IState> {

const isLiveMedia = prevDuration === 0
const noDuration = !prevDuration
const isLongerDuration = nextDuration && (prevDuration && nextDuration > prevDuration)
const isLongerDuration = nextDuration && prevDuration && nextDuration > prevDuration

if (nextDuration && !isLiveMedia && (noDuration || isLongerDuration)) {
this.props.dispatch(updateMedia({ duration: nextDuration }))
Expand Down Expand Up @@ -547,7 +547,8 @@ class _VideoPlayer extends PureComponent<PrivateProps, IState> {
className={cx(styles.video, {
[styles.interactive]: this.state.interacting,
[styles.playing]: !!this.props.current,
[styles.mediaReady]: this.state.mediaReady
[styles.mediaReady]: this.state.mediaReady,
[styles.audioOnly]: this.props.playerSettings.audioMode,
})}
allowScripts
popup={this.shouldRenderPopup}
Expand Down Expand Up @@ -606,7 +607,7 @@ class _VideoPlayer extends PureComponent<PrivateProps, IState> {
if (this.webview) {
this.webview.loadURL(this.mediaUrl, {
httpReferrer: this.httpReferrer,
userAgent: MEDIA_SESSION_USER_AGENT
userAgent: MEDIA_SESSION_USER_AGENT,
})
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ export default class AppearanceSettings extends Component<Props> {
onChange={checked => setSetting('theaterMode', checked)}
/>

<SwitchOption
inputId="audiomode"
title={t('audioMode')}
description={t('audioModeDesc')}
checked={settings.audioMode}
onChange={checked => setSetting('audioMode', checked)}
/>

<SwitchOption
inputId="dock_chat"
title={t('uiDockToRight')}
Expand Down
2 changes: 2 additions & 0 deletions packages/metastream-app/src/components/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useDispatch } from 'react-redux'
import { setLobbyModal } from 'actions/ui'
import { LobbyModal } from 'reducers/ui'
import { MediaList } from 'components/lobby/MediaList'
import { FavoritesList } from 'components/lobby/FavoritesList'
import { PanelHeader } from 'components/lobby/PanelHeader'
import { ChatLayoutButton } from 'components/chat/ChatLayoutButton'
import { t } from 'locale'
Expand Down Expand Up @@ -41,6 +42,7 @@ export const Sidebar: React.SFC<Props> = ({ className, popup }) => {
}}
collapsible
/>
<FavoritesList className={styles.list} />
<PanelHeader title={t('chat')} action={popup ? null : <ChatLayoutButton />} />
<Chat className={styles.chat} showDockOption={false} />
</div>
Expand Down
3 changes: 2 additions & 1 deletion packages/metastream-app/src/constants/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export const enum StorageKey {
AutoplayNotice = 'autoplayNotice',
HasInteracted = 'hasInteracted',
Login = 'login',
TipsDismissed = 'tipsDismissed'
TipsDismissed = 'tipsDismissed',
Favorites = 'favorites',
}
5 changes: 4 additions & 1 deletion packages/metastream-app/src/locale/en-US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default {
donate: 'Donate',
donators: 'Donators',
duplicate: 'Duplicate',
addFavorite: 'Add to favorites',
embedBlocked:
'To enable playback with <1>{{host}}</1>, Metastream must open the website in a popup.',
endSessionTitle: 'End Session?',
Expand Down Expand Up @@ -134,12 +135,15 @@ export default {
theaterMode: 'Theater mode',
theaterModeDesc:
'Hide all non-video content on the webpage. Note that this might also hide soft subtitles.',
audioMode: 'Audio mode',
audioModeDesc: 'Hide video and play audio only.',
thirdPartyIntegrations: 'Third-party Integrations',
toggleDJ: 'Toggle DJ',
uiDockToRight: 'Dock to right side',
uiUndock: 'Undock into floating overlays',
unlimited: 'Unlimited',
unlockQueue: 'Unlock queue',
favorites: 'Favorites',
updateAvailable:
'An update for Metastream is available. Press the UPDATE button in the top-right to receive the update.',
username: 'Username',
Expand All @@ -153,5 +157,4 @@ export default {
welcome: 'Welcome',
welcomeToMetastream: 'Welcome to Metastream',
welcomeMessage1: 'Hi, thanks for trying out Metastream!',

}
Loading