Code Snippets for xCams
Performer Controller Code Samples
import {
Controller,
Injectable,
UseGuards,
Body,
Post,
HttpCode,
HttpStatus,
UsePipes,
ValidationPipe,
Put,
Get,
Param,
Query,
Request,
UseInterceptors,
Res,
HttpException
} from '@nestjs/common';
import { Response, Request as Req } from 'express';
import {
DataResponse,
PageableData,
getConfig,
EntityNotFoundException,
QueueEventService,
QueueEvent
} from 'src/kernel';
import { AuthService } from 'src/modules/auth/services';
import { Roles, CurrentUser } from 'src/modules/auth/decorators';
import { UserInterceptor } from 'src/modules/auth/interceptors';
import { RoleGuard, AuthGuard } from 'src/modules/auth/guards';
import { FileUploadInterceptor, FileUploaded, FileDto } from 'src/modules/file';
import { UserDto } from 'src/modules/user/dtos';
import { FavouriteService } from 'src/modules/favourite/services';
import { SettingService } from 'src/modules/settings';
import { SETTING_KEYS } from 'src/modules/settings/constants';
import { CountryService } from 'src/modules/utils/services';
import {
DELETE_FILE_TYPE,
FileService,
FILE_EVENT,
MEDIA_FILE_CHANNEL
} from 'src/modules/file/services';
import { omit } from 'lodash';
import { EXCLUDE_FIELDS } from 'src/kernel/constants';
import { AccountNotFoundxception } from 'src/modules/user/exceptions';
import { PasswordIncorrectException } from 'src/modules/auth/exceptions';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { PerformerBroadcastSetting } from '../payloads/performer-broadcast-setting.payload';
import { PERFORMER_STATUSES } from '../constants';
import { PerformerDto, IPerformerResponse, BlockSettingDto } from '../dtos';
import {
PerformerUpdatePayload,
PerformerSearchPayload,
PerformerStreamingStatusUpdatePayload,
BlockSettingPayload,
DefaultPricePayload
} from '../payloads';
import { PerformerService, PerformerSearchService } from '../services';
@Injectable()
@Controller('performers')
@ApiTags('Performer')
export class PerformerController {
constructor(
private readonly authService: AuthService,
private readonly performerService: PerformerService,
private readonly performerSearchService: PerformerSearchService,
private readonly favoriteService: FavouriteService,
private readonly settingService: SettingService,
private readonly countryService: CountryService,
private readonly fileService: FileService,
private readonly queueEventService: QueueEventService
) {}
@Get('/me')
@HttpCode(HttpStatus.OK)
// @Roles('performer')
// @UseGuards(RoleGuard)
async me(@Request() request: Req): Promise<DataResponse<IPerformerResponse>> {
const jwtToken = request.headers.authorization;
const performer = await this.authService.getSourceFromJWT(jwtToken);
if (!performer || performer.status !== PERFORMER_STATUSES.ACTIVE) {
throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
}
const result = await this.performerService.getDetails(
performer._id,
jwtToken
);
return DataResponse.ok(new PerformerDto(result).toResponse(true));
}
@Get('/search')
@HttpCode(HttpStatus.OK)
@UseInterceptors(UserInterceptor)
@UsePipes(new ValidationPipe({ transform: true }))
async usearch(
@Query() req: PerformerSearchPayload,
@CurrentUser() user: UserDto,
@Request() request: Req
): Promise<DataResponse<PageableData<IPerformerResponse>>> {
const query = { ...req };
// only query activated performer, sort by online time
query.status = PERFORMER_STATUSES.ACTIVE;
let ipClient =
request.headers['x-forwarded-for'] || request.connection.remoteAddress;
ipClient = Array.isArray(ipClient) ? ipClient.toString() : ipClient;
if (ipClient.substr(0, 7) === '::ffff:') {
ipClient = ipClient.substr(7);
}
// const ipClient = '115.75.211.252';
const whiteListIps = ['127.0.0.1', '0.0.0.1'];
let userCountry = null;
let countryCode = null;
if (whiteListIps.indexOf(ipClient) === -1) {
userCountry = await this.countryService.findCountryByIP(ipClient);
if (
userCountry &&
userCountry.status === 'success' &&
userCountry.countryCode
) {
countryCode = userCountry.countryCode;
}
}
const data = await this.performerSearchService.advancedSearch(
query,
user,
countryCode
);
return DataResponse.ok({
total: data.total,
data: data.data
});
}
@Put('/')
@Roles('performer')
@UseGuards(RoleGuard)
@ApiSecurity('authorization')
@ApiOperation({ summary: 'Update Performer'})
async updatePerformer(
@CurrentUser() currentPerformer: PerformerDto,
@Body() payload: PerformerUpdatePayload,
@Request() request: Req
): Promise<DataResponse<IPerformerResponse>> {
await this.performerService.update(
currentPerformer._id,
omit(payload, EXCLUDE_FIELDS)
);
const performer = await this.performerService.getDetails(
currentPerformer._id,
request.headers.authorization
);
return DataResponse.ok(new PerformerDto(performer).toResponse(true));
}
@Get('/:username/view')
@UseInterceptors(UserInterceptor)
@HttpCode(HttpStatus.OK)
async getDetails(
@Param('username') performerUsername: string,
@Request() req: Req,
@CurrentUser() user: UserDto
): Promise<DataResponse<Partial<PerformerDto>>> {
let ipClient =
req.headers['x-forwarded-for'] || req.connection.remoteAddress;
ipClient = Array.isArray(ipClient) ? ipClient.toString() : ipClient;
if (ipClient.substr(0, 7) === '::ffff:') {
ipClient = ipClient.substr(7);
}
// const ipClient = '115.75.211.252';
const whiteListIps = ['127.0.0.1', '0.0.0.1'];
let userCountry = null;
let countryCode = null;
if (whiteListIps.indexOf(ipClient) === -1) {
userCountry = await this.countryService.findCountryByIP(ipClient);
if (
userCountry &&
userCountry.status === 'success' &&
userCountry.countryCode
) {
countryCode = userCountry.countryCode;
}
}
const performer = await this.performerService.findByUsername(
performerUsername,
countryCode,
user
);
if (!performer || performer.status !== PERFORMER_STATUSES.ACTIVE) {
throw new EntityNotFoundException();
}
if (user) {
const favorite = await this.favoriteService.findOne({
favoriteId: performer._id,
ownerId: user._id
});
if (favorite) performer.isFavorite = true;
}
const [defaultGroupChatPrice, defaultC2CPrice] = await Promise.all([
this.settingService.getKeyValue(SETTING_KEYS.GROUP_CHAT_DEFAULT_PRICE) || 0,
this.settingService.getKeyValue(SETTING_KEYS.PRIVATE_C2C_PRICE) || 0
]);
performer.privateCallPrice = typeof performer.privateCallPrice !== 'undefined' ? performer.privateCallPrice : defaultC2CPrice;
performer.groupCallPrice = typeof performer.groupCallPrice !== 'undefined' ? performer.groupCallPrice : defaultGroupChatPrice;
return DataResponse.ok(performer.toPublicDetailsResponse());
}
@Post('/documents/upload')
@HttpCode(HttpStatus.OK)
@Roles('performer')
@UseGuards(RoleGuard)
@UseInterceptors(
FileUploadInterceptor('performer-document', 'file', {
destination: getConfig('file').documentDir
})
)
async uploadPerformerDocument(
@FileUploaded() file: FileDto,
@CurrentUser() performer: PerformerDto,
@Request() request: Req
): Promise<any> {
return DataResponse.ok({
...file,
url: `${file.getUrl()}?documentId=${file._id}&token=${
request.headers.authorization
}`
});
}
@Post('/release-form/upload')
@HttpCode(HttpStatus.OK)
@Roles('performer')
@UseGuards(RoleGuard)
@UseInterceptors(
FileUploadInterceptor('performer-release-form', 'file', {
destination: getConfig('file').documentDir
})
)
async uploadPerformerReleaseForm(
@FileUploaded() file: FileDto,
@CurrentUser() performer: PerformerDto,
@Request() request: Req
): Promise<any> {
return DataResponse.ok({
...file,
url: `${file.getUrl()}?documentId=${file._id}&token=${
request.headers.authorization
}`
});
}
@Post('/avatar/upload')
@HttpCode(HttpStatus.OK)
@Roles('performer')
@UseGuards(RoleGuard)
@UseInterceptors(
FileUploadInterceptor('avatar', 'avatar', {
destination: getConfig('file').avatarDir,
generateThumbnail: true,
replaceWithThumbail: true,
thumbnailSize: getConfig('image').avatar
})
)
async uploadPerformerAvatar(
@FileUploaded() file: FileDto,
@CurrentUser() performer: PerformerDto
): Promise<any> {
await this.performerService.updateAvatar(performer._id, file);
await this.fileService.addRef(file._id, {
itemId: performer._id,
itemType: 'performer-avatar'
});
await this.queueEventService.publish(
new QueueEvent({
channel: MEDIA_FILE_CHANNEL,
eventName: FILE_EVENT.FILE_RELATED_MODULE_UPDATED,
data: {
type: DELETE_FILE_TYPE.FILEID,
currentFile: performer.avatarId,
newFile: file._id
}
})
);
return DataResponse.ok({
...file,
url: file.getUrl()
});
}
@Post('/streaming-status/update')
@HttpCode(HttpStatus.OK)
@Roles('performer')
@UseGuards(RoleGuard)
@UsePipes(new ValidationPipe({ transform: true }))
async updateStreamingStatus(
@CurrentUser() currentPerformer: PerformerDto,
@Body() payload: PerformerStreamingStatusUpdatePayload
): Promise<DataResponse<IPerformerResponse>> {
await this.performerService.updateSteamingStatus(
currentPerformer._id,
payload.status || ''
);
const performer = await this.performerService.findById(
currentPerformer._id
);
return DataResponse.ok(new PerformerDto(performer).toResponse(true));
}
@Post('/default-price/update')
@HttpCode(HttpStatus.OK)
@Roles('performer')
@UseGuards(RoleGuard)
@UsePipes(new ValidationPipe({ transform: true }))
async updateDefaultPrice(
@CurrentUser() currentPerformer: PerformerDto,
@Body() payload: DefaultPricePayload
): Promise<DataResponse<IPerformerResponse>> {
await this.performerService.updateDefaultPrice(
currentPerformer._id,
payload
);
const performer = await this.performerService.findById(
currentPerformer._id
);
return DataResponse.ok(new PerformerDto(performer).toResponse(true));
}
@Post('/broadcast-setting/update')
@HttpCode(HttpStatus.OK)
@Roles('performer')
@UseGuards(RoleGuard)
@UsePipes(new ValidationPipe({ transform: true }))
async updateBroadcastSetting(
@CurrentUser() currentPerformer: PerformerDto,
@Body() payload: PerformerBroadcastSetting
): Promise<DataResponse<IPerformerResponse>> {
await this.performerService.updateBroadcastSetting(
currentPerformer._id,
payload
);
const performer = await this.performerService.findById(
currentPerformer._id
);
return DataResponse.ok(new PerformerDto(performer).toResponse(true));
}
@Get('/documents/auth/check')
@HttpCode(HttpStatus.OK)
async checkAuth(
@Request() request: Req,
@Res() response: Response
): Promise<Response> {
if (!request.query.token) {
return response.status(HttpStatus.UNAUTHORIZED).send();
}
const user = await this.authService.getSourceFromJWT(
request.query.token as string
);
if (!user) {
return response.status(HttpStatus.UNAUTHORIZED).send();
}
const valid = await this.performerService.checkAuthDocument(request, user);
return response
.status(valid ? HttpStatus.OK : HttpStatus.UNAUTHORIZED)
.send();
}
}
Performer Details / Live Page Code Samples
/* eslint-disable camelcase */
import React, { PureComponent } from 'react';
import Head from 'next/head';
import {
Row, Col, Button, message, ButtonProps
} from 'antd';
import { connect } from 'react-redux';
import { IPerformer, IUser } from 'src/interfaces';
import { messageService, streamService } from 'src/services';
import { LivePublisher } from '@components/streaming/publisher';
import { SocketContext, Event } from 'src/socket';
import {
getStreamConversationSuccess,
loadStreamMessages,
resetStreamMessage,
resetAllStreamMessage,
resetStreamConversation
} from '@redux/stream-chat/actions';
import { updateStreamingStatus } from '@redux/performer/actions';
import { WEBRTC_ADAPTOR_INFORMATIONS } from 'src/antmedia/constants';
import ChatBox from '@components/stream-chat/chat-box';
import UpdateSatusForm from '@components/performer/streaming-status-update-form';
import Router from 'next/router';
import { getResponseError } from '@lib/utils';
import './index.less';
// eslint-disable-next-line no-shadow
enum EVENT_NAME {
ROOM_INFORMATIOM_CHANGED = 'public-room-changed',
USER_LEFT_ROOM = 'USER_LEFT_ROOM'
}
interface P {
resetStreamMessage: Function;
resetAllStreamMessage: Function;
getStreamConversationSuccess: Function;
loadStreamMessages: Function;
updateStreamingStatus: Function;
activeConversation: any;
performer: IPerformer;
updating: boolean;
updateSuccess: boolean;
updateError: any;
resetStreamConversation: Function;
}
interface S {
loading: boolean;
fetching: boolean;
initialized: boolean;
publish_started: boolean;
total: number;
members: IUser[];
}
class PerformerLivePage extends PureComponent<P, S> {
static authenticate = true;
private publisherRef: any;
private btnRef: React.RefObject<HTMLElement> = React.createRef();
private socket: SocketIOClient.Socket;
constructor(props: P) {
super(props);
this.state = {
loading: false,
fetching: false,
initialized: false,
publish_started: false,
total: 0,
members: []
};
}
componentDidMount() {
this.socket = this.context;
this.joinPublicRoom();
window.addEventListener('beforeunload', this.onbeforeunload);
Router.events.on('routeChangeStart', this.onbeforeunload);
}
componentDidUpdate(prevProps: P) {
const { updateSuccess, updateError } = this.props;
if (prevProps.updateSuccess !== updateSuccess && updateSuccess) {
message.success('Update Status Success.');
}
if (prevProps.updateError !== updateError && updateError) {
message.error(getResponseError(updateError));
}
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.onbeforeunload);
Router.events.off('routeChangeStart', this.onbeforeunload);
}
handler({ total, members, conversationId }) {
const { activeConversation } = this.props;
if (activeConversation?.data?._id === conversationId) {
this.setState({ total, members });
}
}
handleUpdateStatusForm(data) {
const { updateStreamingStatus: dispatchUpdateStreamingStatus } = this.props;
dispatchUpdateStreamingStatus(data);
}
onbeforeunload = () => {
this.leavePublicRoom();
};
start() {
this.publisherRef && this.publisherRef.start();
}
stop() {
const { initialized, publish_started } = this.state;
if (!initialized || !publish_started) {
window.location.reload();
return;
}
if (window.confirm('Are you sure want to stop this live streaming!')) {
window.location.reload();
}
}
async callback(info: WEBRTC_ADAPTOR_INFORMATIONS) {
const { activeConversation } = this.props;
if (activeConversation && activeConversation.data) {
this.socket = this.context;
if (info === WEBRTC_ADAPTOR_INFORMATIONS.INITIALIZED) {
this.setState({ initialized: true });
try {
this.setState({ loading: true });
const resp = await streamService.goLive();
this.publisherRef && this.publisherRef.publish(resp.data.sessionId);
} catch (e) {
const error = await Promise.resolve(e);
message.error(getResponseError(error));
this.setState({ loading: false });
}
} else if (info === WEBRTC_ADAPTOR_INFORMATIONS.PUBLISH_STARTED) {
this.setState({ publish_started: true, loading: false });
this.socket.emit('public-stream/live', {
conversationId: activeConversation.data._id
});
} else if (info === WEBRTC_ADAPTOR_INFORMATIONS.PUBLISH_FINISHED) {
this.setState({ loading: false, publish_started: false });
} else if (info === WEBRTC_ADAPTOR_INFORMATIONS.CLOSED) {
this.setState({
loading: false,
initialized: false,
publish_started: false
});
}
}
}
async joinPublicRoom() {
const {
loadStreamMessages: dispatchLoadStreamMessages,
getStreamConversationSuccess: dispatchGetStreamConversationSuccess
} = this.props;
try {
this.setState({ fetching: true });
const resp = await streamService.goLive();
const { conversation } = resp.data;
if (conversation && conversation._id) {
// this.publisherRef && this.publisherRef.start();
dispatchGetStreamConversationSuccess({ data: conversation });
dispatchLoadStreamMessages({
conversationId: conversation._id,
limit: 25,
offset: 0,
type: conversation.type
});
this.socket = this.context;
this.socket
&& this.socket.emit('public-stream/join', {
conversationId: conversation._id
});
}
} catch (e) {
const error = await Promise.resolve(e);
message.error(getResponseError(error));
} finally {
this.setState({ fetching: false });
}
}
leavePublicRoom() {
const {
activeConversation,
resetStreamMessage: dispatchResetStreamMessage,
resetStreamConversation: dispatchResetStreamConversation
} = this.props;
if (activeConversation && activeConversation.data) {
const conversation = { ...activeConversation.data };
this.socket && this.socket.emit('public-stream/leave', {
conversationId: conversation._id
});
dispatchResetStreamMessage();
dispatchResetStreamConversation();
}
if (this.btnRef.current) {
this.btnRef.current.remove();
}
}
userLeftRoomHandle({ username, conversationId }) {
const { activeConversation } = this.props;
if (activeConversation?.data?._id === conversationId) {
const { total, members } = this.state;
this.setState({
total: total - 1,
members: members.filter((m) => m.username !== username)
});
}
}
async removeAllMessage() {
const {
activeConversation,
performer,
resetAllStreamMessage: dispatchResetAllMessage
} = this.props;
if (
!activeConversation.data
|| performer._id !== activeConversation.data.performerId
) {
return;
}
try {
if (!window.confirm('Are you sure you want to remove chat history?')) {
return;
}
await messageService.deleteAllMessageInConversation(
activeConversation.data._id
);
dispatchResetAllMessage({ conversationId: activeConversation.data._id });
} catch (e) {
const error = await Promise.resolve(e);
message.error(getResponseError(error));
}
}
render() {
const { performer, activeConversation, updating } = this.props;
const {
loading, initialized, publish_started, members, total, fetching
} = this.state;
const btnProps: ButtonProps & React.RefAttributes<HTMLElement> = {
ref: this.btnRef,
loading,
disabled: fetching,
block: true
};
if (initialized && publish_started) {
btnProps.type = 'text';
btnProps.style = { marginBottom: 10, background: 'black', color: 'white' };
btnProps.children = 'Stop broadcasting';
btnProps.onClick = this.stop.bind(this);
} else {
btnProps.type = 'primary';
btnProps.style = { marginBottom: 10 };
btnProps.children = 'Start broadcasting';
btnProps.onClick = this.start.bind(this);
}
return (
<>
<Head>
<title>Go Live</title>
</Head>
<Event
event={EVENT_NAME.ROOM_INFORMATIOM_CHANGED}
handler={this.handler.bind(this)}
/>
<Event
event={EVENT_NAME.USER_LEFT_ROOM}
handler={this.userLeftRoomHandle.bind(this)}
/>
<Row>
<Col xs={24} sm={24} md={12}>
<UpdateSatusForm
status={performer.streamingTitle}
updating={updating}
submit={this.handleUpdateStatusForm.bind(this)}
/>
<Button {...btnProps} />
<LivePublisher
ref={(ref) => {
this.publisherRef = ref;
}}
onChange={this.callback.bind(this)}
configs={{
debug: true,
bandwidth: 900,
localVideoId: 'publisher'
}}
/>
</Col>
<Col xs={24} sm={24} md={12}>
<ChatBox
{...this.props}
members={members}
totalParticipant={total}
currentPerformer={performer}
/>
{activeConversation?.data && (
<div style={{ margin: '10px' }}>
<Button
type="primary"
onClick={this.removeAllMessage.bind(this)}
>
Clear message history
</Button>
</div>
)}
</Col>
</Row>
</>
);
}
}
PerformerLivePage.contextType = SocketContext;
const mapStateToProps = (state) => ({
performer: state.performer.current,
updating: state.performer.updating,
updateSuccess: state.performer.updateSuccess,
updateError: state.performer.updateError,
activeConversation: state.streamMessage.activeConversation
});
const mapDispatchs = {
updateStreamingStatus,
getStreamConversationSuccess,
loadStreamMessages,
resetStreamMessage,
resetAllStreamMessage,
resetStreamConversation
};
export default connect(mapStateToProps, mapDispatchs)(PerformerLivePage);
Â
Â