首页 > 其他分享 >在线录屏-通过Web API接口轻松实现录屏

在线录屏-通过Web API接口轻松实现录屏

时间:2023-11-08 14:13:14浏览次数:46  
标签:Web include const MediaDevices mediaRecorder 录屏 API font

在线录屏是指在互联网上进行屏幕录制的过程。它允许用户通过网络连接,将自己的屏幕活动记录下来,并可以在需要时进行播放、共享或存档。在线录屏常用于教育、培训、演示、游戏等场景,可以帮助用户展示操作步骤、解决问题、分享经验等。通常,在线录屏工具提供了丰富的功能,例如选择录制区域、添加音频注释、调整录制质量等,以满足用户的不同需求。

在线体验地址:https://amd794.com/recordscreen

工具演示视频:https://www.bilibili.com/video/BV1wC4y1U7at/

<iframe allowfullscreen="allowfullscreen" frameborder="no" height="248" scrolling="no" src="//player.bilibili.com/player.html?bvid=BV1wC4y1U7at&cid=1324713522&p=1&share_source=copy_web" style="width: 496px; height: 248px" width="496"></iframe>

 

相关说明:

MediaDevices 是 Web API 中的一部分,它提供了访问媒体设备(如摄像头、麦克风等)的功能。通过 MediaDevices 接口,您可以使用 JavaScript 代码来获取和操作媒体设备的流(如音频和视频)。这使得您可以在 Web 应用程序中实现音视频通信、媒体录制和流媒体等功能。

当使用 MediaDevices 接口调用 Web API 时,可以按照以下示例代码进行操作:

// 检查浏览器是否支持 MediaDevices 接口
if (navigator.mediaDevices) {
  // 获取媒体设备的用户授权
  navigator.mediaDevices.getUserMedia({ audio: true, video: true })
    .then(function(stream) {
      // 获取到媒体流后的处理逻辑
      // 在这里可以对获取到的音视频流进行操作
      // 例如:显示视频流、播放音频流等
    })
    .catch(function(error) {
      // 处理获取媒体设备失败的情况
      console.log('获取媒体设备失败: ' + error.message);
    });
} else {
  console.log('您的浏览器不支持 MediaDevices 接口');
}

上述示例代码首先检查浏览器是否支持 MediaDevices 接口。如果支持,它会调用 `getUserMedia` 方法来请求用户授权访问媒体设备(包括音频和视频)。如果用户授权成功,将返回一个媒体流对象,可以在 `.then` 方法中对该流进行处理。如果用户拒绝授权或发生其他错误,将在 `.catch` 方法中处理错误情况。

通过这样的调用方式,您可以使用 MediaDevices 接口来获取媒体设备的流,并对其进行进一步的操作和处理。

 

浏览器兼容性:

可以说是大部分不支持,毕竟是新的Web API接口,详细可以去MDN中查看MediaDevices - Web API 接口参考 | MDN (mozilla.org)

相关实现代码:

潦草布局一下,毕竟只是个玩具,不需要多华丽。

  <div class="RecordScreen">
    <div class="Operate">
      <div class="Content">
        <div class="flex Start" @click="onStart" v-if="!setState">
          <el-icon size="50px" color=" var(--el-color-primary)">
            <VideoCameraFilled/>
          </el-icon>
          <el-button type="primary">{{ currentLang.start }}</el-button>
        </div>
        <div class="flex Pause" @click="onPause" v-if="setState === 'recording'">
          <el-icon size="50px" color=" var(--el-color-primary)">
            <VideoPause/>
          </el-icon>
          <el-button type="info">{{ currentLang.stop }}</el-button>
        </div>
        <div class="flex Resume" @click="onResume" v-if="setState === 'paused'">
          <el-icon size="50px" color=" var(--el-color-primary)">
            <VideoPlay/>
          </el-icon>
          <el-button type="info">{{ currentLang.continue }}</el-button>
        </div>
        <div class="flex Stop" @click="onStop" v-if="setState">
          <el-icon size="50px" color=" var(--el-color-primary)">
            <SwitchButton/>
          </el-icon>
          <el-button type="danger">{{ currentLang.end }}</el-button>
        </div>
      </div>
      <el-divider v-if="VideoURL"/>
      <div class="Operate__Download">
        <div v-if="!VideoURL"
             style="width: 100vw;height: 100vh;position: absolute;top: 0;left: 0;z-index: 99999;background-color: var(--el-bg-color);">
        </div>
        <el-input v-model="DownloadName" placeholder="please input">
          <template #append>.mp4</template>
        </el-input>
        <el-button type="primary" :icon="Download" @click="onDownload">{{ currentLang.saveVideo }}</el-button>
      </div>
    </div>
    <div class="RecordScreen__state">
      <div v-if="setState" class="REC">
        <div></div>
        <div>REC</div>
      </div>
      <el-icon v-if="!VideoURL" size="40vh" color="var(--el-color-primary)">
        <Monitor/>
      </el-icon>
      <div v-if="setState" class="Timing">
        <div>{{ Hour }}:{{ Minute }}:{{ Seconds }}</div>
        <div>{{ setState === 'paused' ? currentLang.pauseScreenRecording : currentLang.recordingScreen }}</div>
      </div>
      <video v-if="VideoURL" :src="VideoURL" controls></video>
    </div>
  </div>

简单定义几个变量

const VideoURL = ref('')
const DownloadName = ref('')
const setState = ref('')
let mediaRecorder = null
let mediaThen = null

开始录屏

const onStart = () => {
  VideoURL.value = ''
  mediaThen = null
  navigator.mediaDevices.getDisplayMedia({video: true, audio: true})
      .then(mediaStream => {
        mediaThen = mediaStream
        mediaRecorder = new MediaRecorder(mediaStream);
        Timing()
        console.log(mediaRecorder)
        const chunks = [];
        mediaRecorder.ondataavailable = event => {
          if (event.data.size > 0) {
            chunks.push(event.data);
          }
        };
        mediaRecorder.onstop = () => {
          const blob = new Blob(chunks, {type: 'video/mp4'});
          const recordedVideoURL = URL.createObjectURL(blob);
          VideoURL.value = recordedVideoURL
          setState.value = '' //inactive
        };
        mediaRecorder.start();
        setState.value = mediaRecorder.state   //recording


      })
      .catch(error => {
        // if (error == 'DOMException: Permission denied') return false
        // ElMessageBox({
        //   title: '当前浏览器或设备不支持录屏',
        //   confirmButtonText: '确定',
        //   message: h('p', null, [
        //     h('span', null, error),
        //   ]),
        // })
      });
}

暂停录屏

const onPause = () => {
  clearTimeout(timer)
  mediaRecorder.pause()
  setState.value = mediaRecorder.state   //paused
}

恢复录屏

const onResume = () => {
  Timing()
  mediaRecorder.resume()
  setState.value = mediaRecorder.state   //recording
}

结束录屏

const onStop = () => {
  mediaRecorder.stop()
  mediaThen.getTracks().forEach(track => track.stop())
  setState.value = '' //inactive
}

最后把样式补上


<style scoped lang="scss">
@mixin Flex {
  display: flex;
  align-items: center;
  justify-content: center;
}

@mixin side {
  padding: 10px;
  gap: 10px;
}


@mixin font($size: 0.9rem, $Color: red, $spacing: 0.2rem) {
  font-size: $size;
  line-height: $size;
  color: $Color;
  letter-spacing: $spacing;
}


.RecordScreen {
  @font-face {
    font-family: digital-display;
    src: url(data:font/woff2;base64,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)
  }

  $bg: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIGlkPSLlm77lsYJfMSIgeD0iMHB4IgogICAgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMzQ2IDE0NyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzQ2IDE0NzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgogICAgPGcgc3R5bGU9Im9wYWNpdHk6IDAuMSI+CiAgICAgIDxwYXRoIHN0eWxlPSJmaWxsOiAjN0NGMUZEOyIKICAgICAgICBkPSJNMzM2LDg1LjNjLTg3LjktMS41LTcxLjEtNTguMS05MC43LTU3LjhjLTEzLjgsMC4yLTEzLjYsMzkuNi0zNSwzOC45QzE4NS4yLDY1LjYsMTkyLjcsMCwxNzAuMSwwICAgYy0yMi45LDAtMTYuOCw3NS4yLTQ4LjcsNzQuMmMtMzAuMi0wLjgtMzAuMS0zNC4zLTM3LjUtMzQuM2MtNy4zLDAtOC44LDQyLjUtMzUsNDIuNWMtMTguMywwLTE3LjMtMTcuNC0yNi43LTE3LjQgICBDMTMuOCw2NS4xLDE0LjUsODAsMCw4NC40djAuNWgyOThoMzhWODUuM3oiIC8+CiAgICAgIDxwYXRoIHN0eWxlPSJmaWxsOiAjMDA4NEZGOyIKICAgICAgICBkPSJNMzQyLjIsODUuM2MtMzQuMSwwLTM0LjIsMjItNDguNSwyMi4yYy0xMS4zLDAuMi0xNC0xOS40LTM5LjctMTguOWMtMzMuOSwwLjctMjYuNiw1OC40LTQ2LjMsNTguNCAgIGMtMTguOSwwLTEyLjgtNTkuMS00NC4zLTU5LjFjLTMyLjYsMC0yNS4yLDM5LjYtMzcsMzkuNmMtMTAuNSwwLTUuNS0zOS40LTM2LjItMzkuOWMtMjUuOS0wLjMtMjkuOSwxMC4xLTQyLjcsOS45ICAgQzMwLjEsOTcuMywzMS4yLDg1LjEsNi41LDg1LjFoMzM1LjdWODUuM3oiIC8+CiAgICAgIDxwYXRoIHN0eWxlPSJvcGFjaXR5OiAwLjU7ZmlsbDogIzAwRUFGRjtlbmFibGUtYmFja2dyb3VuZDogbmV3OyIKICAgICAgICBkPSJNMzQ2LDg1LjNjLTM4LjcsMC00My41LTE1LjktNTYuMy0xNi4xYy0xMC0wLjItMTQsMTAuMS00NC4yLDkuNGMtMzQuMS0wLjctMjMuNC00MC40LTQwLjktNDAuNCAgIGMtMTYuNiwwLTguNSw0Mi0zNi4yLDQxLjlDMTQ2LDgwLDE1MC41LDUyLjgsMTM2LjIsNTIuOGMtMTMuMywwLTEzLjgsMzAuMy0zMy45LDMwLjZDODUsODMuNiw3NS43LDY3LDY1LjYsNjcgICBjLTEyLjEsMC0xNi4zLDE4LjItMzkuNSwxOC4ySDM0NnoiIC8+CiAgICA8L2c+CiAgPC9zdmc+';


  @include Flex;
  justify-content: space-around;
  height: 100vh;
  overflow: hidden;
  box-shadow: var(--el-box-shadow);
  position: relative;
  flex-direction: column-reverse;

  .flex {
    @include Flex;
    flex-direction: column;
  }

  .Operate {
    .Content {
      @include Flex;

      .Start {
        @include side;
      }

      .Pause {
        @include side;
      }

      .Resume {
        @include side;
      }

      .Stop {
        @include side;
      }
    }

    &__Download {
      @include Flex;
      gap: 10px;
      position: relative;
      overflow: hidden;
    }
  }

  &__state {
    position: relative;
    border-radius: 1rem;
    @include Flex;
    justify-content: space-around;
    width: 57vmax;
    height: 70vh;
    border: 1px dashed var(--el-color-primary);
    background: #022125 url($bg) center no-repeat;
    overflow: hidden;

    .REC {
      @include Flex;
      gap: 0.5rem;
      position: absolute;
      top: 0.9rem;
      left: 0.9rem;

      :first-child {
        width: 1rem;
        height: 1rem;
        border-radius: 50rem;
        background-color: red;
      }

      :last-child {
        @include font()
      }
    }


    .Timing {
      display: flex;
      align-items: flex-start;
      gap: 1rem;
      flex-direction: column;

      :first-child {
        @include font(2rem, #1fb5c9, 0.3rem);
        font-weight: 600;
        font-family: digital-display;
      }

      :last-child {
        @include font(1.8rem, #1fb5c9, 0.3rem);
        font-weight: 600;
      }
    }

    video {
      width: 100%;
      height: 100%;
      vertical-align: middle;

    }
  }
}
</style>

 

标签:Web,include,const,MediaDevices,mediaRecorder,录屏,API,font
From: https://www.cnblogs.com/Amd794/p/17816413.html

相关文章

  • [-006-]-Python3+Unittest+Selenium Web UI自动化测试之悬浮窗口中的元素点击
     1.分析现状:PPT模板悬浮出现悬浮窗口悬浮窗口中分为4大类:PPT模板,PPT模板页,PPT关系图,PPT图表大类下存在小类点击可跳转但是此页面里还存在PPT模板下的总结汇报等此种情况的元素此情况如果仅用text定位是无法定位到的所以排除了text定位方式2.解决方法:首先我们看下悬浮窗......
  • 依赖注入与API接口访问速度
    手上有个项目,客户反馈说一个接口访问慢。查了接口调用的sql,发现并不是sql影响接口。本地调试发现,从网页点击开始访问到实际进入接口函数花费了很多时间,通过与其他接口对比。发现该控制器通过Ioc注入了较多的服务接口。于是删减不必要接口及通过其他方式调用接口,使改接口访问时......
  • Web Profile Builder for Web Application Projects
    WebProfileBuilderforWebApplicationProjectsFilescanbedownloadedfromtheWebProfileBuilderprojectpage.IfyouuseWebApplicationProjects,youhaveprobablyrunintotheissueofnotbeingabletoaccesstheProfileatdesigntime.Thankfully......
  • .net core webapi Startup services.AddHttpClient
    staticpublicclassCreditScoreServiceExtension{staticpublicvoidAddCreditScoreQueryServiceHttpClient(thisIServiceCollectionservices,IConfigurationconfig){services.AddSingleton<ICreditScoreQueryService,CreditS......
  • x-admin-web
    1 解压 vue-admin-template-master2 改名:x-admin-web3 openprojectbyIDEA:  D:\idea_code\demo_qinqin_11014  npminstall--registry=https://registry.npm.taobao.orgview-->apperance--->toolbar 勾选 配置configuration npm -Local:http://loca......
  • 从零开始构建报警中心:part04 钉钉消息-webhook
    现在工作上比较常用的IM一般式钉钉企微飞书,其实使用起来都是大同小异的。这里就用钉钉来实现。使用钉钉发送信息,一般有三种形式群webhook工作通知智能机器人智能机器人方式,能实现一定的交互功能,但逻辑相对复杂,这里只是需要一个实时的钉钉消息,所以不进行讨论。添加群webhook这是一......
  • beego框架 golang web框架-网上花店
    beego框架golangweb框架-网上花店beego网上花店功能介绍主页商品列表展示商品详情用户登录注册购买购物车评价用户中心订单列表后台管理页商品管理添加修改删除商品用户管理添加删除用户网上花店功能比较简单适合刚接触beego的初学者使用技术beego框架My......
  • ArcGIS API for JavaScript入门
    arcgis官网:https://arcgis.fenxianglu.cn/docs/load.html一、集成到应用----@arcgis/core方式1、引入ArcGISAPIforJavaScriptyarnadd@arcgis/core或者指定安装版本yarnadd@arcgis/[email protected]如果提示:'yarn'不是内部或外部命令,也不是可运行的程序或批处理文件......
  • .netCoreApi5.0+部署iis站点webconfig配置 HTTP 错误 500.24 - Internal Server Error
     <?xmlversion="1.0"encoding="utf-8"?><configuration><locationpath="."inheritInChildApplications="false"><system.webServer><handlers><addname="aspN......
  • Asp.Net Core webapi+net6 使用资源筛选器(过滤器) 做缓存
    写一个特性类,用来做标记[AttributeUsage(AttributeTargets.Method)]//只对方法有效publicclassResourceFilterAttribute:Attribute{}我这里使用了MemoryCache来做缓存,也可以使用字典来做,但一定要加上static,否则字典每一次请求都会new一个实例,缓存的东西就丢了private......