音频
This commit is contained in:
File diff suppressed because it is too large
Load Diff
620
pages/component/commonComponents/video/cx-audio-play copy.vue
Normal file
620
pages/component/commonComponents/video/cx-audio-play copy.vue
Normal file
@@ -0,0 +1,620 @@
|
||||
<template>
|
||||
<!--音频组件-->
|
||||
<view
|
||||
class="boxShadow"
|
||||
style="
|
||||
width: 100%;
|
||||
border-radius: 40rpx;
|
||||
height: 100%;
|
||||
padding: 40rpx 0;
|
||||
box-sizing: border-box;
|
||||
background-image: linear-gradient(80deg, #959698 0%, #808183 100%);
|
||||
"
|
||||
>
|
||||
<view class="bgfff">
|
||||
<view>
|
||||
<slot name="title"></slot>
|
||||
<view class="audo-video">
|
||||
<view class="slider-box">
|
||||
<text class="mm">{{ timer }}</text>
|
||||
|
||||
<slider
|
||||
style="width: 440rpx"
|
||||
@change="sliderChange"
|
||||
@changing="sliderChanging"
|
||||
class="audio-slider"
|
||||
block-size="16"
|
||||
:min="0"
|
||||
:max="duration"
|
||||
:value="currentTime"
|
||||
activeColor="#FF8849"
|
||||
@touchstart="lock = true"
|
||||
@touchend="lock = false"
|
||||
/>
|
||||
<text class="ss" v-if="overTimer != 'NaN:NaN'">{{
|
||||
overTimer
|
||||
}}</text>
|
||||
<text class="ss" v-else>00.00</text>
|
||||
</view>
|
||||
<!--音频播放按钮处-->
|
||||
<view class="audo-top">
|
||||
<!--上一首-->
|
||||
<!-- <image
|
||||
src="./icon/xys.png"
|
||||
style="width: 40rpx; height: 40rpx; transform: rotate(180deg)"
|
||||
mode="aspectFill"
|
||||
@click="upper(1)"
|
||||
></image> -->
|
||||
<!--上一首-->
|
||||
|
||||
<!--快退-->
|
||||
<image
|
||||
src="./icon/kt.png"
|
||||
style="width: 40rpx; height: 40rpx"
|
||||
mode="widthFix"
|
||||
@click="jump('kt')"
|
||||
></image>
|
||||
<!--快退-->
|
||||
|
||||
<!--播放-->
|
||||
<image
|
||||
src="./icon/kt.png"
|
||||
style="width: 100rpx; height: 100rpx"
|
||||
mode="widthFix"
|
||||
@click="plays()"
|
||||
></image>
|
||||
<!--播放-->
|
||||
|
||||
<!--快进-->
|
||||
<image
|
||||
src="./icon/kj.png"
|
||||
style="width: 40rpx; height: 40rpx"
|
||||
mode="widthFix"
|
||||
@click="jump('kj')"
|
||||
></image>
|
||||
<!--快进-->
|
||||
|
||||
<!--下一首-->
|
||||
<!-- <image
|
||||
src="./icon/xys.png"
|
||||
style="width: 40rpx; height: 40rpx"
|
||||
mode="aspectFill"
|
||||
@click="upper(2)"
|
||||
></image> -->
|
||||
<!--下一首-->
|
||||
|
||||
<view class="audo-a" style="">
|
||||
<!--进度条-->
|
||||
|
||||
<!--进度条-->
|
||||
|
||||
<!--倍数-->
|
||||
<view
|
||||
class="beishu"
|
||||
style="border: 1rpx solid #e1dbf2"
|
||||
@click="beishu"
|
||||
>{{ BsNav[bsindex].bs }} X
|
||||
</view>
|
||||
<view class="absolute" style="right: 0; top: 80rpx" v-if="shows">
|
||||
<!-- <u-transition :show="show" mode="slide-right" duration="300"> -->
|
||||
<view
|
||||
class="beishu-a"
|
||||
style="border: 1rpx solid #fff"
|
||||
v-if="show"
|
||||
>
|
||||
<view
|
||||
v-for="(item, index) in BsNav"
|
||||
:key="index"
|
||||
@click="setRate(index, item)"
|
||||
class="title beishuItem"
|
||||
:style="{ color: bsid == item.id ? '#9461EB' : '' }"
|
||||
>
|
||||
{{ item.bs }} X
|
||||
</view>
|
||||
</view>
|
||||
<!-- </u-transition> -->
|
||||
</view>
|
||||
<!--倍数-->
|
||||
</view>
|
||||
</view>
|
||||
<!--音频播放按钮处-->
|
||||
|
||||
<!--音频api处[视频代替音频-实现倍数功能]-->
|
||||
<video
|
||||
id="myVideo"
|
||||
ref="myVideo"
|
||||
:src="recorPath"
|
||||
preload="metadata"
|
||||
@timeupdate="timeupdate"
|
||||
:autoplay="autoplays"
|
||||
@loadedmetadata="loadedmetadata"
|
||||
@ended="next"
|
||||
controls
|
||||
style="width: 400rpx; height: 80rpx"
|
||||
></video>
|
||||
<!--音频api处[视频代替音频-实现倍数功能]-->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!--占位-->
|
||||
</view>
|
||||
<!--音频组件 wx:cxalq8-24 Author:chenxin-->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/*
|
||||
list -- 音频数据: 数组格式/不传无法播放
|
||||
Seconds -- 快进快退秒数: 默认15秒
|
||||
autoNext -- 是否自动播放下一首
|
||||
autoplays -- 进入页面是否自动播放 - 默认false
|
||||
slideYes -- 滑动进度条时是否开启播放 - 默认false
|
||||
switAud -- 切换上下音频是否开启播放 - 默认true
|
||||
BsNav -- 倍数数据传入/数组形式
|
||||
|
||||
图片功能样式,如想改动请在组件内部修改,页面中已注释 -- 逻辑根据自己需求改
|
||||
目前只测试 微信小程序和H5和APP -- 其他平台未知
|
||||
[Author:chenxin 交流vx:cxalq8-24]
|
||||
*/
|
||||
export default {
|
||||
name: "cx-audio-play",
|
||||
props: {
|
||||
list: {
|
||||
//音频数据
|
||||
Type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
Seconds: {
|
||||
//快进快退 - 秒数
|
||||
Type: Number,
|
||||
default: 15,
|
||||
},
|
||||
firstTime: {
|
||||
//快进快退 - 秒数
|
||||
Type: Number,
|
||||
default: 0,
|
||||
},
|
||||
autoNext: {
|
||||
//是否自动播放下一首
|
||||
Type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
autoplays: {
|
||||
//是否开启自动播放
|
||||
Type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
slideYes: {
|
||||
//滑动进度条 - 是否开启播放
|
||||
Type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
switAud: {
|
||||
Type: Boolean, //切换上下音频 - 是否开启播放
|
||||
default: true,
|
||||
},
|
||||
BsNav: {
|
||||
//倍数-传入
|
||||
Type: Array,
|
||||
default: () => [
|
||||
{
|
||||
id: 1.0,
|
||||
bs: "1.0",
|
||||
},
|
||||
{
|
||||
id: 1.5,
|
||||
bs: "1.5",
|
||||
},
|
||||
{
|
||||
id: 2.0,
|
||||
bs: "2.0",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
shows: false, //倍数弹框
|
||||
show: true, //倍数弹框动画默认开启
|
||||
succes: false, //播放按钮
|
||||
bsid: "", //倍数默认显示第一个
|
||||
bsindex: 0, //倍数默认显示第一个
|
||||
num: 0,
|
||||
current: 0, //当前选中的索引
|
||||
recorPath: "", //音频播放地址
|
||||
lock: false, // 锁
|
||||
currentTime: 0, //当前进度
|
||||
duration: 0, // 总进度
|
||||
videoContext: null,
|
||||
loading: true, //锁 加载
|
||||
curTime: null, //锁 加载
|
||||
};
|
||||
},
|
||||
onReady() {},
|
||||
onShow() {},
|
||||
mounted() {
|
||||
//初始化音频api
|
||||
},
|
||||
updated() {},
|
||||
onLoad() {},
|
||||
onHide() {
|
||||
//监听页面离开 - 销毁音频
|
||||
this.stop_();
|
||||
this.$emit("curTime", this.curTime);
|
||||
},
|
||||
onUnload() {
|
||||
//监听页面卸载 - 销毁音频
|
||||
this.stop_();
|
||||
this.$emit("curTime", this.curTime);
|
||||
},
|
||||
destroyed() {
|
||||
// this.innerAudioContext.stop();
|
||||
},
|
||||
computed: {
|
||||
timer() {
|
||||
this.curTime = this.currentTime.toFixed(0);
|
||||
return calcTimer(this.currentTime);
|
||||
},
|
||||
overTimer() {
|
||||
return calcTimer(this.duration);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.currentTime = this.firstTime;
|
||||
this.videoContext = uni.createVideoContext("myVideo", this);
|
||||
//默认播放第一个
|
||||
if (this.list.length != 0) {
|
||||
this.recorPath = this.list[0];
|
||||
if (this.autoplays) {
|
||||
this.succes = true;
|
||||
}
|
||||
} else {
|
||||
this.duration = 0;
|
||||
}
|
||||
//倍数默认选择第一个
|
||||
if (this.BsNav.length != 0) {
|
||||
this.bsid = this.BsNav[0].id;
|
||||
this.playbackRate(); //倍数
|
||||
}
|
||||
},
|
||||
//没有音频数据
|
||||
noUrl() {
|
||||
if (this.list.length == 0) {
|
||||
uni.showToast({
|
||||
title: "请先传入音频数据哦~",
|
||||
icon: "none",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
//播放暂停
|
||||
plays() {
|
||||
if (!this.noUrl()) {
|
||||
return;
|
||||
}
|
||||
this.playLoading();
|
||||
this.succes = !this.succes;
|
||||
this.succes ? this.play_() : this.pause_();
|
||||
},
|
||||
|
||||
//上下首切换
|
||||
upper(num) {
|
||||
if (!this.noUrl()) {
|
||||
return;
|
||||
}
|
||||
let that = this;
|
||||
if (num == 1) {
|
||||
//上一首
|
||||
if (this.num == 0) {
|
||||
this.upperToast(num); //提示
|
||||
return;
|
||||
}
|
||||
this.num -= 1;
|
||||
} else {
|
||||
//下一首
|
||||
if (this.num + 1 == this.list.length) {
|
||||
this.upperToast(num); //提示
|
||||
return;
|
||||
}
|
||||
this.num += 1;
|
||||
}
|
||||
this.recorPath = this.list[this.num];
|
||||
if (this.switAud) {
|
||||
// 切换时是否开启播放
|
||||
this.succes = true;
|
||||
this.play_();
|
||||
} else {
|
||||
this.succes = false;
|
||||
}
|
||||
this.playbackRate(); //倍数
|
||||
console.log("当前音频:", this.recorPath);
|
||||
},
|
||||
|
||||
//快进快退
|
||||
jump(e) {
|
||||
if (!this.noUrl()) {
|
||||
return;
|
||||
}
|
||||
var num;
|
||||
if (e == "kt") {
|
||||
num = (this.currentTime - Math.floor(this.Seconds % 60)).toFixed(0); //当前时间-15秒
|
||||
} else {
|
||||
num = (this.currentTime + Math.floor(this.Seconds % 60)).toFixed(0); //当前时间+15秒
|
||||
}
|
||||
this.seek(num);
|
||||
},
|
||||
seek(num) {
|
||||
this.$nextTick(() => {
|
||||
//#ifdef H5
|
||||
this.$refs.myVideo.seek(num);
|
||||
//#endif
|
||||
//#ifndef H5
|
||||
this.videoContext.seek(num);
|
||||
//#endif
|
||||
});
|
||||
},
|
||||
|
||||
//倍速弹框
|
||||
beishu() {
|
||||
this.shows = !this.shows;
|
||||
},
|
||||
// 倍速
|
||||
setRate(index, item) {
|
||||
this.bsid = item.id;
|
||||
this.bsindex = index;
|
||||
this.shows = false;
|
||||
this.playbackRate();
|
||||
},
|
||||
|
||||
// 更新进度条
|
||||
timeupdate(event) {
|
||||
if (this.lock) return; // 锁
|
||||
var currentTime, duration;
|
||||
if (event.detail.detail) {
|
||||
currentTime = event.detail.detail.currentTime;
|
||||
duration = event.detail.detail.duration;
|
||||
} else {
|
||||
currentTime = event.detail.currentTime;
|
||||
duration = event.detail.duration;
|
||||
}
|
||||
this.currentTime = currentTime;
|
||||
console.log("this.currentTime at line 當前386:", this.currentTime);
|
||||
this.duration = duration;
|
||||
console.log("this.duration at line 總時長388:", this.duration);
|
||||
},
|
||||
|
||||
// 拖动进度条
|
||||
sliderChange(data) {
|
||||
if (!this.noUrl()) {
|
||||
return;
|
||||
}
|
||||
//此处滑动进度条--开始播放
|
||||
if (this.slideYes && !this.succes) {
|
||||
this.play_();
|
||||
this.succes = true;
|
||||
}
|
||||
//#ifdef H5
|
||||
this.$refs.myVideo.seek(data.detail.value); //获取秒数
|
||||
//#endif
|
||||
//#ifndef H5
|
||||
this.videoContext.seek(data.detail.value); //获取秒数
|
||||
//#endif
|
||||
},
|
||||
|
||||
//拖动中
|
||||
sliderChanging(data) {
|
||||
if (data.detail.value == 0) {
|
||||
this.succes = false;
|
||||
this.pause_();
|
||||
}
|
||||
this.currentTime = data.detail.value;
|
||||
},
|
||||
|
||||
// 视频加载完成
|
||||
loadedmetadata(data) {
|
||||
console.log("data at line zh:", data);
|
||||
this.duration = data.detail.duration;
|
||||
|
||||
// this.seek(this.firstTime);
|
||||
// this.curTime = this.currentTime
|
||||
},
|
||||
|
||||
//音频结束触发
|
||||
next(data) {
|
||||
this.succes = false;
|
||||
if (this.autoNext) {
|
||||
//自动播放下一首
|
||||
this.upper(2);
|
||||
}
|
||||
},
|
||||
|
||||
// 各平台播放_暂停
|
||||
play_() {
|
||||
this.$nextTick(() => {
|
||||
//#ifdef H5
|
||||
this.$refs.myVideo.play();
|
||||
//#endif
|
||||
//#ifndef H5
|
||||
this.videoContext.play();
|
||||
//#endif
|
||||
});
|
||||
},
|
||||
pause_() {
|
||||
this.$nextTick(() => {
|
||||
//#ifdef H5
|
||||
this.$refs.myVideo.pause();
|
||||
//#endif
|
||||
//#ifndef H5
|
||||
this.videoContext.pause();
|
||||
//#endif
|
||||
});
|
||||
},
|
||||
|
||||
//离开页面暂停播放
|
||||
stop_() {
|
||||
this.$nextTick(() => {
|
||||
//#ifdef H5
|
||||
this.$refs.myVideo.stop();
|
||||
//#endif
|
||||
//#ifndef H5
|
||||
this.videoContext.stop();
|
||||
//#endif
|
||||
});
|
||||
},
|
||||
|
||||
//提示
|
||||
upperToast(num) {
|
||||
num == 1
|
||||
? uni.showToast({
|
||||
title: "到头了~",
|
||||
icon: "none",
|
||||
})
|
||||
: uni.showToast({
|
||||
title: "没有更多了~",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
|
||||
//音频倍数
|
||||
playbackRate() {
|
||||
this.$nextTick(() => {
|
||||
//#ifdef H5
|
||||
this.$refs.myVideo.playbackRate(this.bsid);
|
||||
//#endif
|
||||
//#ifndef H5
|
||||
this.videoContext.playbackRate(this.bsid);
|
||||
//#endif
|
||||
});
|
||||
},
|
||||
|
||||
//加载框--封
|
||||
playLoading() {
|
||||
if (this.loading && !this.succes) {
|
||||
uni.showLoading({
|
||||
title: "音频加载中...",
|
||||
});
|
||||
this.loading = false;
|
||||
}
|
||||
setTimeout(() => {
|
||||
uni.hideLoading();
|
||||
}, 1500);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
//播放器当前播放进度
|
||||
curTime(val) {
|
||||
if (this.curTime !== null) {
|
||||
console.log("timer at line 495:", this.curTime);
|
||||
this.$emit("curTime", this.curTime);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
//时间换算
|
||||
function calcTimer(timer) {
|
||||
if (timer === 0 || typeof timer !== "number") {
|
||||
return "00:00";
|
||||
}
|
||||
let mm = Math.floor(timer / 60);
|
||||
let ss = Math.floor(timer % 60);
|
||||
if (mm < 10) {
|
||||
mm = "0" + mm;
|
||||
}
|
||||
if (ss < 10) {
|
||||
ss = "0" + ss;
|
||||
}
|
||||
return mm + ":" + ss;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
/* #video {
|
||||
width: 100%;
|
||||
} */
|
||||
.audo-video {
|
||||
margin-top: 0rpx;
|
||||
color: #fff;
|
||||
.beishuItem {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.slider-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 27rpx;
|
||||
// color: #fff;
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline-block;
|
||||
width: 100rpx;
|
||||
background-color: #fff;
|
||||
font-size: 24rpx;
|
||||
color: #000;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: -10rpx;
|
||||
z-index: -1;
|
||||
width: 1rpx;
|
||||
height: 1rpx;
|
||||
}
|
||||
|
||||
.audo-top {
|
||||
padding: 20rpx 0;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
|
||||
image {
|
||||
width: 45rpx;
|
||||
height: 45rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.audo-a {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
position: relative;
|
||||
z-index: 9;
|
||||
margin-right: -20rpx;
|
||||
}
|
||||
|
||||
.beishu {
|
||||
position: relative;
|
||||
width: 100rpx;
|
||||
padding-top: 5rpx;
|
||||
padding-bottom: 5rpx;
|
||||
text-align: center;
|
||||
border-radius: 25rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.absolute {
|
||||
position: absolute;
|
||||
|
||||
.beishu-a {
|
||||
width: 200rpx;
|
||||
border-radius: 20rpx;
|
||||
text-align: center;
|
||||
line-height: 90rpx;
|
||||
background: #fff;
|
||||
|
||||
.title {
|
||||
pdding-left: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -17,8 +17,9 @@
|
||||
<view class="audo-video">
|
||||
<view class="slider-box">
|
||||
<text class="mm">{{ timer }}</text>
|
||||
|
||||
<slider
|
||||
style="width: calc(100% - 280rpx)"
|
||||
style="width: 440rpx"
|
||||
@change="sliderChange"
|
||||
@changing="sliderChanging"
|
||||
class="audio-slider"
|
||||
@@ -222,7 +223,7 @@ export default {
|
||||
recorPath: "", //音频播放地址
|
||||
lock: false, // 锁
|
||||
currentTime: 0, //当前进度
|
||||
duration: 100, // 总进度
|
||||
duration: 0, // 总进度
|
||||
videoContext: null,
|
||||
loading: true, //锁 加载
|
||||
curTime: null, //锁 加载
|
||||
@@ -238,30 +239,29 @@ export default {
|
||||
onHide() {
|
||||
//监听页面离开 - 销毁音频
|
||||
this.stop_();
|
||||
this.$emit("curTime", this.curTime);
|
||||
this.$emit("curTime", this.curTime);
|
||||
},
|
||||
onUnload() {
|
||||
|
||||
//监听页面卸载 - 销毁音频
|
||||
this.stop_();this.$emit("curTime", this.curTime);
|
||||
this.stop_();
|
||||
this.$emit("curTime", this.curTime);
|
||||
},
|
||||
destroyed() {
|
||||
// this.innerAudioContext.stop();
|
||||
},
|
||||
computed: {
|
||||
timer() {
|
||||
this.curTime = this.currentTime;
|
||||
this.curTime = this.currentTime;
|
||||
return calcTimer(this.currentTime);
|
||||
},
|
||||
overTimer() {
|
||||
this.curTime = this.duration;
|
||||
return calcTimer(this.duration);
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
init() {
|
||||
this.currentTime=this.firstTime;
|
||||
this.currentTime = this.firstTime;
|
||||
this.videoContext = uni.createVideoContext("myVideo", this);
|
||||
//默认播放第一个
|
||||
if (this.list.length != 0) {
|
||||
@@ -277,9 +277,6 @@ export default {
|
||||
this.bsid = this.BsNav[0].id;
|
||||
this.playbackRate(); //倍数
|
||||
}
|
||||
this.seek(this.firstTime);
|
||||
this.curTime = this.currentTime
|
||||
|
||||
},
|
||||
//没有音频数据
|
||||
noUrl() {
|
||||
@@ -384,7 +381,9 @@ export default {
|
||||
duration = event.detail.duration;
|
||||
}
|
||||
this.currentTime = currentTime;
|
||||
console.log("this.currentTime at line 當前386:", this.currentTime);
|
||||
this.duration = duration;
|
||||
console.log("this.duration at line 總時長388:", this.duration);
|
||||
},
|
||||
|
||||
// 拖动进度条
|
||||
@@ -416,7 +415,11 @@ export default {
|
||||
|
||||
// 视频加载完成
|
||||
loadedmetadata(data) {
|
||||
console.log("data at line zh:", data);
|
||||
this.duration = data.detail.duration;
|
||||
|
||||
// this.seek(this.firstTime);
|
||||
// this.curTime = this.currentTime
|
||||
},
|
||||
|
||||
//音频结束触发
|
||||
@@ -512,8 +515,7 @@ export default {
|
||||
};
|
||||
//时间换算
|
||||
function calcTimer(timer) {
|
||||
console.log('timer at line 514:', timer)
|
||||
|
||||
console.log("timer at line 514:", timer);
|
||||
|
||||
if (timer === 0 || typeof timer !== "number") {
|
||||
return "00:00";
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
BIN
pages/component/commonComponents/video/icon/zanting.jpg
Normal file
BIN
pages/component/commonComponents/video/icon/zanting.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 816 B |
BIN
pages/component/commonComponents/video/icon/zt.jpg
Normal file
BIN
pages/component/commonComponents/video/icon/zt.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 809 B |
@@ -2,7 +2,7 @@
|
||||
<view
|
||||
class="container"
|
||||
id="Aliyun"
|
||||
style="background-color: #000; position: relative; padding: 0"
|
||||
:style="`background-color:# 000; position: relative; padding: 0`"
|
||||
>
|
||||
<div
|
||||
:videoData="videoData"
|
||||
|
||||
Reference in New Issue
Block a user