warrior před 4 dny
rodič
revize
5c4179bef2

+ 1 - 0
angular.json

@@ -131,6 +131,7 @@
             "tsConfig": "projects/live-app/tsconfig.app.json",
             "inlineStyleLanguage": "scss",
             "assets": [
+              "projects/live-app/src/assets/js/AgoraRTC_N-4.14.0.js",
               {
                 "glob": "**/*",
                 "input": "projects/live-app/public"

+ 7 - 0
projects/live-app/src/app/components/live/live.component.html

@@ -0,0 +1,7 @@
+<div class="video_box" id="video-box">
+  <!-- <div class="camera_box"> -->
+    <canvas id="canvas" width="100%" height="100%" style="display:none"></canvas>
+    <div class="video" id="video" width="100%" height="100%">
+    </div>
+  <!-- </div> -->
+</div>

+ 16 - 0
projects/live-app/src/app/components/live/live.component.scss

@@ -0,0 +1,16 @@
+.video_box {
+  background-color: #ffca22;
+  width: 100%;
+  height: 100vh;
+  // border-radius: 2px;
+  // border: 0.5px solid #a1a1a1;
+  .camera_box {
+    // margin-top: 10px;
+    position: relative;
+    .video {
+      // border-radius: 8px;
+      width: 100%;
+      height: 100%;
+    }
+  }
+}

+ 28 - 0
projects/live-app/src/app/components/live/live.component.spec.ts

@@ -0,0 +1,28 @@
+/* tslint:disable:no-unused-variable */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+
+import { LiveComponent } from './live.component';
+
+describe('LiveComponent', () => {
+  let component: LiveComponent;
+  let fixture: ComponentFixture<LiveComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ LiveComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(LiveComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 145 - 0
projects/live-app/src/app/components/live/live.component.ts

@@ -0,0 +1,145 @@
+import { Component, OnInit } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import * as Parse from 'parse';
+import {
+  IonicModule,
+  LoadingController,
+  ToastController,
+} from '@ionic/angular';
+import { ActivatedRoute } from '@angular/router';
+import { AiChatService } from '../../../services/aichart.service';
+import { HttpService } from '../../../services/http.service';
+declare const AgoraRTC: any;
+
+@Component({
+  selector: 'app-live',
+  templateUrl: './live.component.html',
+  styleUrls: ['./live.component.scss'],
+  standalone: true,
+  imports: [IonicModule, FormsModule],
+})
+export class LiveComponent implements OnInit {
+  profile?:Parse.Object
+  rid?: string;
+  room?: Parse.Object;
+  client: any //客户端
+  options:{appid:string,channel:string,uid:string|null,token:string} = {
+    appid: 'f4f19322cdb1412ab44343613bb7535f',
+    channel: '',
+    uid: null,
+    token: ''
+  };
+  localTracks: any = {
+    audioTrack: null,
+    videoTrack: null
+  };
+  constructor(
+    public toastController: ToastController,
+    private loadingCtrl: LoadingController,
+    private activateRoute: ActivatedRoute,
+    private aiSer:AiChatService,
+    private http:HttpService
+  ) { }
+
+  ngOnInit() {
+    this.client = AgoraRTC.createClient({ mode: "live", codec: "h264" });
+    AgoraRTC.enableLogUpload()
+    this.activateRoute.paramMap.subscribe(async (params) => {
+      let rid: any = params.get('rid');
+      this.rid = rid;
+      if (!this.rid) {
+        return;
+      }
+      this.getRoom();
+    });
+  }
+  async getRoom() {
+    let query = new Parse.Query('Room');
+    query.equalTo('objectId', this.rid);
+    query.notEqualTo('isDeleted', true);
+    query.include('user');
+    this.room = await query.first();
+    let queryProfile = new Parse.Query('Profile');
+    query.equalTo('user', this.room?.get('user').id);
+    queryProfile.notEqualTo('isDeleted', true);
+    this.profile = await queryProfile.first();
+  }
+  async getToken() {
+    let baseurl = 'https://server.masterol.cn/api/webrtc/build_token'
+    let reqBody = {
+      company: this.aiSer.company,
+      profile: localStorage.getItem('profileId'),
+      channelName: localStorage.getItem('profileId'),
+      department: localStorage.getItem('department'),
+      exam: localStorage.getItem('exam')
+    }
+
+    let data:any = this.http.httpRequst(baseurl,reqBody)
+    console.log(data)
+    if (data.code == 200) {
+      this.options.token = data.data.token,
+        this.options.appid = data.data.appid,
+        this.options.channel = localStorage.getItem('profileId')??'',
+      this.options.uid = localStorage.getItem('profileId')
+      await this.join()
+    }
+  }
+    // 进入频道
+    async join() {
+      let data = await Promise.all([
+        // join the channel
+        this.client.join(this.options.appid, this.options.channel, this.options.token, 111111),
+  
+        // create local tracks, using microphone and camera
+        AgoraRTC.createMicrophoneAudioTrack(),
+        AgoraRTC.createCameraVideoTrack()
+      ]);
+      await this.client.setClientRole('host')
+      console.log(data);
+      this.localTracks.audioTrack = data[1]
+      this.localTracks.videoTrack = data[2]
+  
+      this.localTracks.videoTrack.play("video");
+  
+      let publish = await this.client.publish(Object.values(this.localTracks));
+  
+      // getCurrentFrameData 获取当前渲染的视频帧数据。
+      // 订阅监考端音视频
+      this.client.on('user-published', async (user:any, mediaType:any) => {
+        console.log('user-published')
+        await this.client.subscribe(user, mediaType)
+        if (mediaType == "audio" && user.uid != 333333) {
+          console.log(mediaType, user)
+          //远端老师发出语音对话提示
+          // this.createBasicNotification('请注意,监考老师已向你发出语音通话!', 'info', '语音通话提示')
+          const remoteAudioTrack = user.audioTrack
+          remoteAudioTrack.setVolume(100)
+          remoteAudioTrack.play()
+        }
+      })
+      // 查询远端用户是否存在小程序端用户
+      this.joinReady()
+    }
+    async joinReady() {
+      console.log(this.client.remoteUsers)
+      let wxRemoteUsers = this.client.remoteUsers.find((item:any) => {
+        if (item.uid && item._video_added_) {
+          return item
+        }
+      })
+      console.log(wxRemoteUsers)
+      this.client.on("user-published", async (user:any, mediaType:any) => {
+        if(user.uid == 333333) {
+          await this.client.subscribe(user, mediaType);
+          console.log('用户推流成功')
+      
+        }
+      })
+      this.client.on("user-unpublished", (user:any) => {
+        if(user.uid == 333333) {
+          console.log('用户取消推流')
+    
+        }
+      })
+    }
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4 - 0
projects/live-app/src/assets/js/AgoraRTC_N-4.14.0.js


+ 1 - 0
projects/live-app/src/index.html

@@ -8,6 +8,7 @@
   <link rel="icon" type="image/x-icon" href="favicon.ico">
   <script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
   <script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
+  <script src="./assets/js/AgoraRTC_N-4.14.0.js"></script>
 </head>
 <body>
   <app-root></app-root>

+ 3 - 3
projects/live-app/src/moduls/live/chat/chat.component.html

@@ -7,7 +7,7 @@
       ></ion-icon>
     </ion-buttons>
     <ion-title class="title">{{ targetUser?.get('nickname') }}</ion-title>
-    <ion-buttons slot="end" id="click-trigger">
+    <ion-buttons slot="end" id="option-trigger">
       <ion-icon name="ellipsis-horizontal-outline"></ion-icon>
     </ion-buttons>
   </ion-toolbar>
@@ -18,11 +18,11 @@
   (ionScrollStart)="handleScrollStart()"
   (ionScroll)="handleScroll($any($event))"
   (ionScrollEnd)="handleScrollEnd()"
-  class="ion-padding"
+  class="content"
   (click)="changeShowEmoji(true)"
 >
   <ion-popover
-    trigger="click-trigger"
+    trigger="option-trigger"
     [dismissOnSelect]="true"
     triggerAction="click"
   >

+ 1 - 1
projects/live-app/src/moduls/live/chat/chat.component.scss

@@ -1,4 +1,4 @@
-.ion-padding {
+.content {
   padding-bottom: 50px;
   .avatar {
     width: 40px;

+ 0 - 1
projects/live-app/src/moduls/live/chat/chat.component.ts

@@ -89,7 +89,6 @@ export class ChatComponent implements OnInit {
   constructor(
     private http: HttpClient,
     public datePipe: DatePipe,
-    private modalController: ModalController,
     public toastController: ToastController,
     private loadingCtrl: LoadingController,
     private activateRoute: ActivatedRoute,

+ 23 - 0
projects/live-app/src/moduls/live/link-page/link-page.component.html

@@ -0,0 +1,23 @@
+<ion-content class="content">
+  <div class="nav-data">
+    <div class="room-data">
+          <img class="avatar" [src]="room?.get('user')?.get('avatar')" alt="avatar" />
+      <div class="profile-title">
+        <div class="profile-name">{{ room?.get('user')?.get('nickname') }}</div>
+        <div class="level">LV:33</div>
+      </div>
+      <ion-icon
+      (click)="onCollection()"
+      name="heart-circle-sharp"
+      [style.color]="isFollow ? '#fe454e' : '#fff'"
+    ></ion-icon>
+    </div>
+    <div class="report">
+      <ion-icon name="warning-outline"></ion-icon>
+    </div>
+    <div class="exit" (click)="onExit()">
+      <ion-icon name="enter-outline"></ion-icon>
+    </div>
+  </div>
+  <app-live></app-live>
+</ion-content>

+ 62 - 0
projects/live-app/src/moduls/live/link-page/link-page.component.scss

@@ -0,0 +1,62 @@
+.content {
+  // --padding-bottom: 50px;
+  --background: #fff;
+  .nav-data{
+    position: fixed;
+    top: 20px;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+    padding: 10px;
+    color: white;
+    .room-data{
+      background-color: rgb(0 0 0 / 40%);
+      display: flex;
+      width: 160px;
+      justify-content: space-between;
+      align-items: center;
+      border-radius: 40px;
+      padding: 4px;
+      .avatar{
+        width: 36px;
+        height: 36px;
+      }
+      .profile-title{
+        display: flex;
+        flex-direction: column;
+        align-items: start;
+        justify-content: space-around;
+        margin-left: 4px;
+        flex: 1;
+        .profile-name{
+          font-size: 14px;
+        }
+        .level{
+          background-color: #cc59de;
+          text-align: center;
+          font-size: 12px;
+          border-radius: 20px;
+          padding: 0px 6px;
+        }
+      }
+      ion-icon{
+        margin-left: 10px;
+        font-size: 30px;
+      }
+    }
+    .report,.exit{
+      background-color: rgb(0 0 0 / 40%);
+      color: white;
+      border-radius: 50%;
+      width: 36px;
+      height: 36px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      ion-icon{
+        font-size: 26px;
+      }
+    }
+  }
+}

+ 28 - 0
projects/live-app/src/moduls/live/link-page/link-page.component.spec.ts

@@ -0,0 +1,28 @@
+/* tslint:disable:no-unused-variable */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+
+import { LinkPageComponent } from './link-page.component';
+
+describe('LinkPageComponent', () => {
+  let component: LinkPageComponent;
+  let fixture: ComponentFixture<LinkPageComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ LinkPageComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(LinkPageComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 99 - 0
projects/live-app/src/moduls/live/link-page/link-page.component.ts

@@ -0,0 +1,99 @@
+import { Component, OnInit } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import * as Parse from 'parse';
+import {
+  IonicModule,
+  LoadingController,
+  ModalController,
+  ToastController,
+} from '@ionic/angular';
+import { ActivatedRoute } from '@angular/router';
+import { LiveComponent } from '../../../app/components/live/live.component';
+@Component({
+  selector: 'app-link-page',
+  templateUrl: './link-page.component.html',
+  styleUrls: ['./link-page.component.scss'],
+  standalone: true,
+  imports: [IonicModule, FormsModule,LiveComponent],
+})
+export class LinkPageComponent implements OnInit {
+  currentUser?: Parse.Object = Parse.User.current(); //当前登录用户
+  rid?: string;
+  room?: Parse.Object;
+  isFollow: boolean = false;
+
+  constructor(
+    public toastController: ToastController,
+    private loadingCtrl: LoadingController,
+    private activateRoute: ActivatedRoute
+  ) {}
+
+  ngOnInit() {
+    this.activateRoute.paramMap.subscribe(async (params) => {
+      let rid: any = params.get('rid');
+      this.rid = rid;
+      if (!this.rid) {
+        history.back();
+        return;
+      }
+      this.refresh();
+    });
+  }
+  async refresh() {
+    const loading = await this.loadingCtrl.create({
+      message: '加载中',
+    });
+    this.getRoom();
+    loading.present();
+    loading.dismiss();
+  }
+  async getRoom() {
+    let query = new Parse.Query('Room');
+    query.equalTo('objectId', this.rid);
+    query.notEqualTo('isDeleted', true);
+    query.include('user');
+    this.room = await query.first();
+    this.getFollwState(this.room?.get('user').id);
+  }
+  /* 关注状态 */
+  async getFollwState(uid: string) {
+    let query = new Parse.Query('ProfileRadar');
+    query.equalTo('toUser', this.currentUser?.id);
+    query.equalTo('fromUser', uid);
+    query.notEqualTo('isDeleted', true);
+    query.equalTo('name', '关注');
+    let r = await query.first();
+    this.isFollow = r?.id ? true : false;
+  }
+  /* 关注 */
+  async onCollection() {
+    let query = new Parse.Query('ProfileRadar');
+    query.equalTo('toUser', this.room?.get('user').id);
+    query.equalTo('fromUser', this.currentUser?.id);
+    // query.notEqualTo('isDeleted', true);
+    query.equalTo('name', '关注');
+    let profileRadar = await query.first();
+    if (!profileRadar?.id) {
+      let radar = Parse.Object.extend('ProfileRadar');
+      profileRadar = new radar();
+      profileRadar?.set('toUser', {
+        __type: 'Pointer',
+        className: '_User',
+        objectId: this.room?.get('user').id,
+      });
+      profileRadar?.set('company', {
+        __type: 'Pointer',
+        className: 'Company',
+        objectId: this.currentUser?.get('company')?.id,
+      });
+      profileRadar?.set('fromUser', this.currentUser?.toPointer());
+      profileRadar?.set('name', '关注');
+    }
+    profileRadar?.set('isDeleted', this.isFollow);
+    await profileRadar?.save();
+    this.isFollow = !this.isFollow;
+  }
+  onExit(){
+    history.back()
+  }
+}

+ 5 - 0
projects/live-app/src/moduls/live/live.modules.routes.ts

@@ -1,6 +1,7 @@
 import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
 import { ChatComponent } from './chat/chat.component';
+import { LinkPageComponent } from './link-page/link-page.component';
 import { SearchComponent } from './search/search.component';
 const routes: Routes = [
   {
@@ -11,6 +12,10 @@ const routes: Routes = [
     path: 'search',//搜索
     component: SearchComponent,
   },
+  {
+    path: 'link-room/:rid',//直播页
+    component: LinkPageComponent,
+  },
   
 ]
 @NgModule({

+ 1 - 0
projects/live-app/src/moduls/tabs/home/home.component.html

@@ -43,6 +43,7 @@
     @for (item of roomList; track $index) {
     <div
       class="list-row"
+      (click)="toUrl('live/link-room/' + item.id)"
       [style.background-image]="'url(' + item?.get('cover') + ')'"
     >
       <div class="title-tag">在线聊天</div>

+ 3 - 0
projects/live-app/src/moduls/tabs/home/home.component.ts

@@ -135,4 +135,7 @@ export class HomeComponent implements OnInit {
   search() {
     this.router.navigate(['live/search']);
   }
+  toUrl(url: string){
+    this.router.navigate([url]);
+  }
 }

+ 5 - 5
projects/live-app/src/moduls/user/profile/profile.component.ts

@@ -52,7 +52,7 @@ export class ProfileComponent implements OnInit {
     public toastController: ToastController,
     public loadingCtrl: LoadingController,
     private aiChatServ: AiChatService,
-    private http: HttpService,
+    private http: HttpService
   ) {}
 
   ngOnInit() {
@@ -119,8 +119,8 @@ export class ProfileComponent implements OnInit {
   /* 关注 */
   async onCollection() {
     let query = new Parse.Query('ProfileRadar');
-    query.equalTo('toUser', this.currentUser?.id);
-    query.equalTo('fromUser', this.uid);
+    query.equalTo('toUser', this.uid);
+    query.equalTo('fromUser', this.currentUser?.id);
     // query.notEqualTo('isDeleted', true);
     query.equalTo('name', '关注');
     let profileRadar = await query.first();
@@ -156,14 +156,14 @@ export class ProfileComponent implements OnInit {
   async onSaveBackGround(e: any) {
     let url = e[0]?.url;
     console.log(url);
-    if(!url){
+    if (!url) {
       const toast = await this.toastController.create({
         message: '请上传背景图片',
         color: 'warning',
         duration: 1000,
       });
       toast.present();
-      return
+      return;
     }
     if (!this.profile?.id) {
       let obj = Parse.Object.extend('Profile');

+ 11 - 0
projects/live-app/src/services/http.service.ts

@@ -43,5 +43,16 @@ export class HttpService {
     return this.http.jsonp(url, cb);
   }
 
+  async httpRequst(url:string,reqBody:any,method?:string){
+    let result = await fetch(url,{
+      method:method || 'GET',
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body:JSON.stringify(reqBody) 
+    })
+    let data = await result.json()
+    return data
+  }
 
 }

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů