Docs, keyboard shortcut fixes
This commit is contained in:
parent
d80be3b903
commit
3ade86537d
2
dist/plyr.js
vendored
2
dist/plyr.js
vendored
File diff suppressed because one or more lines are too long
2
dist/plyr.js.map
vendored
2
dist/plyr.js.map
vendored
File diff suppressed because one or more lines are too long
179
readme.md
179
readme.md
@ -66,18 +66,19 @@ Plyr extends upon the standard HTML5 markup so that's all you need for those typ
|
|||||||
#### HTML5 Video
|
#### HTML5 Video
|
||||||
```html
|
```html
|
||||||
<video poster="/path/to/poster.jpg" id="player" controls>
|
<video poster="/path/to/poster.jpg" id="player" controls>
|
||||||
<source src="/path/to/video.mp4" type="video/mp4">
|
<source src="/path/to/video.mp4" type="video/mp4">
|
||||||
<source src="/path/to/video.webm" type="video/webm">
|
<source src="/path/to/video.webm" type="video/webm">
|
||||||
<!-- Captions are optional -->
|
|
||||||
<track kind="captions" label="English captions" src="/path/to/captions.vtt" srclang="en" default>
|
<!-- Captions are optional -->
|
||||||
|
<track kind="captions" label="English captions" src="/path/to/captions.vtt" srclang="en" default>
|
||||||
</video>
|
</video>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### HTML5 Audio
|
#### HTML5 Audio
|
||||||
```html
|
```html
|
||||||
<audio id="player" controls>
|
<audio id="player" controls>
|
||||||
<source src="/path/to/audio.mp3" type="audio/mp3">
|
<source src="/path/to/audio.mp3" type="audio/mp3">
|
||||||
<source src="/path/to/audio.ogg" type="audio/ogg">
|
<source src="/path/to/audio.ogg" type="audio/ogg">
|
||||||
</audio>
|
</audio>
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -101,7 +102,7 @@ Include the `plyr.js` script before the closing `</body>` tag and then call `ply
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="path/to/plyr.js"></script>
|
<script src="path/to/plyr.js"></script>
|
||||||
<script>var player = new Plyr('#player')</script>
|
<script>const player = new Plyr('#player');</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
|
If you want to use our CDN (provided by [Fastly](https://www.fastly.com/)) for the JavaScript, you can use the following:
|
||||||
@ -131,7 +132,7 @@ The SVG sprite is loaded automatically from our CDN (provided by [Fastly](https:
|
|||||||
|
|
||||||
### LESS & SASS/SCSS
|
### LESS & SASS/SCSS
|
||||||
|
|
||||||
You can use `plyr.less` or `plyr.scss` file included in `/src` as part of your build and change variables to suit your design. The LESS and SASS require you to use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you should already) as all declerations use the W3C definitions - e.g. `appearance: none;` will be prefixed to `-webkit-appearance: none;` by autoprefixer.
|
You can use `plyr.less` or `plyr.scss` file included in `/src` as part of your build and change variables to suit your design. The LESS and SASS require you to use the [autoprefixer](https://www.npmjs.com/package/gulp-autoprefixer) plugin (you be should already!) as all declarations use the W3C definitions.
|
||||||
|
|
||||||
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS you write. Check out the JavaScript source for more on this.
|
The HTML markup uses the BEM methodology with `plyr` as the block, e.g. `.plyr__controls`. You can change the class hooks in the options to match any custom CSS you write. Check out the JavaScript source for more on this.
|
||||||
|
|
||||||
@ -320,16 +321,16 @@ Property | Getter | Setter | Description
|
|||||||
`volume` | ✔ | ✔ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1.
|
`volume` | ✔ | ✔ | Gets or sets the volume for the player. The setter accepts a float between 0 and 1.
|
||||||
`muted` | ✔ | ✔ | Gets or sets the muted state of the player. The setter accepts a boolean.
|
`muted` | ✔ | ✔ | Gets or sets the muted state of the player. The setter accepts a boolean.
|
||||||
`speed` | ✔ | ✔ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5.
|
`speed` | ✔ | ✔ | Gets or sets the speed for the player. The setter accepts a value in the options specified in your config. Generally the minimum should be 0.5.
|
||||||
`quality`¹ | ✔ | ✔ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config.
|
`quality`¹ | ✔ | ✔ | Gets or sets the quality for the player. The setter accepts a value from the options specified in your config.
|
||||||
`loop` | ✔ | ✔ | Gets or sets the current loop state of the player. The setter accepts a boolean.
|
`loop` | ✔ | ✔ | Gets or sets the current loop state of the player. The setter accepts a boolean.
|
||||||
`source` | ✔ | ✔ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples.
|
`source` | ✔ | ✔ | Gets or sets the current source for the player. The setter accepts an object. See [source setter](#source-setter) below for examples.
|
||||||
`poster`² | ✔ | ✔ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image.
|
`poster`² | ✔ | ✔ | Gets or sets the current poster image for the player. The setter accepts a string; the URL for the updated poster image.
|
||||||
`autoplay` | ✔ | ✔ | Gets or sets the autoplay state of the player. The setter accepts a boolean.
|
`autoplay` | ✔ | ✔ | Gets or sets the autoplay state of the player. The setter accepts a boolean.
|
||||||
`language` | ✔ | ✔ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include.
|
`language` | ✔ | ✔ | Gets or sets the preferred captions language for the player. The setter accepts an ISO two-letter language code. Support for the languages is dependent on the captions you include.
|
||||||
`pip` | ✔ | ✔ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+.
|
`pip` | ✔ | ✔ | Gets or sets the picture-in-picture state of the player. The setter accepts a boolean. This currently only supported on Safari 10+ on MacOS Sierra+ and iOS 10+.
|
||||||
|
|
||||||
1. YouTube only. HTML5 will follow.
|
*1. YouTube only. HTML5 will follow.*
|
||||||
2. HTML5 only
|
*2. HTML5 only*
|
||||||
|
|
||||||
#### The `.source` setter
|
#### The `.source` setter
|
||||||
|
|
||||||
@ -426,12 +427,12 @@ player.source = {
|
|||||||
Property | Type | Description
|
Property | Type | Description
|
||||||
-------- | ---- | -----------
|
-------- | ---- | -----------
|
||||||
`type` | String | Either `video` or `audio`. *Note:* YouTube and Vimeo are currently not supported as audio sources.
|
`type` | String | Either `video` or `audio`. *Note:* YouTube and Vimeo are currently not supported as audio sources.
|
||||||
`title` | String | *Optional.* Title of the new media. Used for the `aria-label` attribute on the play button, and outer container. YouTube and Vimeo can be populated automatically.
|
`title` | String | *Optional.* Title of the new media. Used for the `aria-label` attribute on the play button, and outer container. YouTube and Vimeo are populated automatically.
|
||||||
`sources` | Array | This is an array of sources. For HTML5 media, the properties of this object are mapped directly to HTML attributes so more can be added to the object if required.
|
`sources` | Array | This is an array of sources. For HTML5 media, the properties of this object are mapped directly to HTML attributes so more can be added to the object if required.
|
||||||
`poster`¹ | String | The URL for the poster image (HTML5 video only).
|
`poster`¹ | String | The URL for the poster image (HTML5 video only).
|
||||||
`tracks`¹ | String | An array of track objects. Each element in the array is mapped directly to a track element and any keys mapped directly to HTML attributes so as in the example above, it will render as `<track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/example_captions_en.vtt" default>` and similar for the French version. Booleans are converted to HTML5 value-less attributes.
|
`tracks`¹ | String | An array of track objects. Each element in the array is mapped directly to a track element and any keys mapped directly to HTML attributes so as in the example above, it will render as `<track kind="captions" label="English" srclang="en" src="https://cdn.selz.com/plyr/1.0/example_captions_en.vtt" default>` and similar for the French version. Booleans are converted to HTML5 value-less attributes.
|
||||||
|
|
||||||
1. HTML5 only
|
*1. HTML5 only*
|
||||||
|
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
@ -504,84 +505,22 @@ YouTube and Vimeo are currently supported and function much like a HTML5 video.
|
|||||||
|
|
||||||
By default, a player will bind the following keyboard shortcuts when it has focus. If you have the `global` option to `true` and there's only one player in the document then the shortcuts will work when any element has focus, apart from an element that requires input.
|
By default, a player will bind the following keyboard shortcuts when it has focus. If you have the `global` option to `true` and there's only one player in the document then the shortcuts will work when any element has focus, apart from an element that requires input.
|
||||||
|
|
||||||
<table class="table" width="100%">
|
Key | Action
|
||||||
<thead>
|
--- | ------
|
||||||
<tr>
|
`0` to `9` | Seek from 0 to 90% respectively
|
||||||
<th width="25%">Key</th>
|
`space` | Toggle playback
|
||||||
<th width="25%">Global</th>
|
`K` | Toggle playback
|
||||||
<th width="50%">Action</th>
|
← | Seek backward by the `seekTime` option
|
||||||
</tr>
|
→ | Seek forward by the `seekTime` option
|
||||||
</thead>
|
↑ | Increase volume
|
||||||
<tbody>
|
↓ | Decrease volume
|
||||||
<tr>
|
`M` | Toggle mute
|
||||||
<td><code>0</code> to <code>9</code></td>
|
`F` | Toggle fullscreen
|
||||||
<td>✔</td>
|
`C` | Toggle captions
|
||||||
<td>Seek from 0 to 90% respectively</td>
|
`L` | Toggle loop
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>space</code></td>
|
|
||||||
<td></td>
|
|
||||||
<td>Toggle playback</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>K</code></td>
|
|
||||||
<td>✔</td>
|
|
||||||
<td>Toggle playback</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>←</code></td>
|
|
||||||
<td></td>
|
|
||||||
<td>Seek backward by the <code>seekTime</code> option</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>→</code></td>
|
|
||||||
<td></td>
|
|
||||||
<td>Seek forward by the <code>seekTime</code> option</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>↑</code></td>
|
|
||||||
<td></td>
|
|
||||||
<td>Increase volume</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>↓</code></td>
|
|
||||||
<td></td>
|
|
||||||
<td>Decrease volume</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>M</code></td>
|
|
||||||
<td>✔</td>
|
|
||||||
<td>Toggle mute</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>F</code></td>
|
|
||||||
<td>✔</td>
|
|
||||||
<td>Toggle fullscreen</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>C</code></td>
|
|
||||||
<td>✔</td>
|
|
||||||
<td>Toggle captions</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>l</code></td>
|
|
||||||
<td></td>
|
|
||||||
<td>Toggle Loop All/No Loop</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>i</code></td>
|
|
||||||
<td></td>
|
|
||||||
<td>Set the start marker of the loop</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td><code>o</code></td>
|
|
||||||
<td></td>
|
|
||||||
<td>Set the end marker of the loop</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
## Streaming
|
## Streaming
|
||||||
|
|
||||||
Because Plyr is an extension of the standard HTML5 video and audio elements, third party streaming plugins can be used with Plyr. Massive thanks to Matias Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a few examples:
|
Because Plyr is an extension of the standard HTML5 video and audio elements, third party streaming plugins can be used with Plyr. Massive thanks to Matias Russitto ([@russitto](https://github.com/russitto)) for working on this. Here's a few examples:
|
||||||
|
|
||||||
- Using [hls.js](https://github.com/dailymotion/hls.js) - [Demo](http://codepen.io/sampotts/pen/JKEMqB)
|
- Using [hls.js](https://github.com/dailymotion/hls.js) - [Demo](http://codepen.io/sampotts/pen/JKEMqB)
|
||||||
@ -589,62 +528,54 @@ Because Plyr is an extension of the standard HTML5 video and audio elements, thi
|
|||||||
- Using [dash.js](https://github.com/Dash-Industry-Forum/dash.js) - [Demo](http://codepen.io/sampotts/pen/BzpJXN)
|
- Using [dash.js](https://github.com/Dash-Industry-Forum/dash.js) - [Demo](http://codepen.io/sampotts/pen/BzpJXN)
|
||||||
|
|
||||||
## Fullscreen
|
## Fullscreen
|
||||||
|
|
||||||
Fullscreen in Plyr is supported by all browsers that [currently support it](http://caniuse.com/#feat=fullscreen).
|
Fullscreen in Plyr is supported by all browsers that [currently support it](http://caniuse.com/#feat=fullscreen).
|
||||||
|
|
||||||
## Browser support
|
## Browser support
|
||||||
|
|
||||||
<table width="100%" style="text-align: center">
|
Plyr supports the last 2 versions of most *modern* browsers. IE11 is also supported.
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>Safari</td>
|
|
||||||
<td>Firefox</td>
|
|
||||||
<td>Chrome</td>
|
|
||||||
<td>Opera</td>
|
|
||||||
<td>IE9</td>
|
|
||||||
<td>IE10+</td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>✔¹</td>
|
|
||||||
<td>✔</td>
|
|
||||||
<td>✔</td>
|
|
||||||
<td>✔</td>
|
|
||||||
<td>API²</td>
|
|
||||||
<td>✔³</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
¹ Mobile Safari on the iPhone forces the native player for `<video>` so no useful customization is possible. `<audio>` elements have volume controls disabled.
|
Browser | Supported
|
||||||
|
------- | ---------
|
||||||
|
Safari | ✔
|
||||||
|
Mobile Safari | ✔¹
|
||||||
|
Firefox | ✔
|
||||||
|
Chrome | ✔
|
||||||
|
Opera | ✔
|
||||||
|
Edge | ✔
|
||||||
|
IE10+ | ✔²
|
||||||
|
IE9 | API only³
|
||||||
|
|
||||||
² Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported (v1.0.28+)
|
*1. Mobile Safari on the iPhone forces the native player for `<video>` unless the `playsinline` attribute is present. Volume controls are also disabled.*
|
||||||
|
*2. Native player used (no support for `<progress>` or `<input type="range">`) but the API is supported (v1.0.28+)*
|
||||||
³ IE10 has no native fullscreen support, fallback can be used (see [options](#options))
|
*3. IE10 has no native fullscreen support, fallback can be used (see [options](#options))*
|
||||||
|
|
||||||
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
|
The `enabled` option can be used to disable certain User Agents. For example, if you don't want to use Plyr for smartphones, you could use:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
enabled: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)
|
{ enabled: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) }
|
||||||
```
|
```
|
||||||
If a User Agent is disabled but supports `<video>` and `<audio>` natively, it will use the native player.
|
If a User Agent is disabled but supports `<video>` and `<audio>` natively, it will use the native player.
|
||||||
|
|
||||||
Any unsupported browsers will display links to download the media if the correct html is used.
|
|
||||||
|
|
||||||
## RangeTouch
|
## RangeTouch
|
||||||
|
|
||||||
Some touch browsers (particularly Mobile Safari on iOS) seem to have issues with `<input type="range">` elements whereby touching the track to set the value doesn't work and sliding the thumb can be tricky. To combat this, I've created [RangeTouch](https://rangetouch.com) which I'd recommend including in your solution. It's a tiny script with a nice benefit for users on touch devices.
|
Some touch browsers (particularly Mobile Safari on iOS) seem to have issues with `<input type="range">` elements whereby touching the track to set the value doesn't work and sliding the thumb can be tricky. To combat this, I've created [RangeTouch](https://rangetouch.com) which I'd recommend including in your solution. It's a tiny script with a nice benefit for users on touch devices.
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
|
||||||
If you find anything weird with Plyr, please let us know using the GitHub issues tracker.
|
If you find anything weird with Plyr, please let us know using the GitHub issues tracker.
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me](http://sampotts.me) with help from the awesome [contributors](https://github.com/sampotts/plyr/graphs/contributors)
|
Plyr is developed by [@sam_potts](https://twitter.com/sam_potts) / [sampotts.me](http://sampotts.me) with help from the awesome [contributors](https://github.com/sampotts/plyr/graphs/contributors)
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
Plyr costs money to run, not my time - I donate that for free but domains, hosting and more. Any help is appreciated...
|
|
||||||
|
Plyr costs money to run, not only my time - I donate that for free but domains, hosting and more. Any help is appreciated...
|
||||||
[Donate to support Plyr](https://www.paypal.me/pottsy/20usd)
|
[Donate to support Plyr](https://www.paypal.me/pottsy/20usd)
|
||||||
|
|
||||||
## Mentions
|
## Mentions
|
||||||
|
|
||||||
- [ProductHunt](https://www.producthunt.com/tech/plyr)
|
- [ProductHunt](https://www.producthunt.com/tech/plyr)
|
||||||
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
- [The Changelog](http://thechangelog.com/plyr-simple-html5-media-player-custom-controls-webvtt-captions/)
|
||||||
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
- [HTML5 Weekly #177](http://html5weekly.com/issues/177)
|
||||||
@ -657,6 +588,7 @@ Plyr costs money to run, not my time - I donate that for free but domains, hosti
|
|||||||
- [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
- [noupe.com](http://www.noupe.com/design/html5-plyr-is-a-responsive-and-accessible-video-player-94389.html)
|
||||||
|
|
||||||
## Used by
|
## Used by
|
||||||
|
|
||||||
- [Selz.com](https://selz.com)
|
- [Selz.com](https://selz.com)
|
||||||
- [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
- [Peugeot.fr](http://www.peugeot.fr/marque-et-technologie/technologies/peugeot-i-cockpit.html)
|
||||||
- [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
- [Peugeot.de](http://www.peugeot.de/modelle/modellberater/208-3-turer/fotos-videos.html)
|
||||||
@ -670,14 +602,17 @@ Plyr costs money to run, not my time - I donate that for free but domains, hosti
|
|||||||
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
|
Let me know on [Twitter](https://twitter.com/sam_potts) I can add you to the above list. It'd be awesome to see how you're using Plyr :-)
|
||||||
|
|
||||||
## Useful links and credits
|
## Useful links and credits
|
||||||
|
|
||||||
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
Credit to the PayPal HTML5 Video player from which Plyr's caption functionality was originally ported from:
|
||||||
- [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
- [PayPal's Accessible HTML5 Video Player](https://github.com/paypal/accessible-html5-video-player)
|
||||||
- [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
- [An awesome guide for Plyr in Japanese!](http://syncer.jp/how-to-use-plyr-io) by [@arayutw](https://twitter.com/arayutw)
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
[](https://www.fastly.com/)
|
[](https://www.fastly.com/)
|
||||||
|
|
||||||
Thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
|
Massive thanks to [Fastly](https://www.fastly.com/) for providing the CDN services.
|
||||||
|
|
||||||
## Copyright and License
|
## Copyright and License
|
||||||
[The MIT license](license.md).
|
|
||||||
|
[The MIT license](license.md)
|
||||||
|
@ -101,7 +101,6 @@ const listeners = {
|
|||||||
case 75:
|
case 75:
|
||||||
// Space and K key
|
// Space and K key
|
||||||
if (!held) {
|
if (!held) {
|
||||||
this.console.warn('togglePlay', event.type);
|
|
||||||
this.togglePlay();
|
this.togglePlay();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -119,7 +118,7 @@ const listeners = {
|
|||||||
case 77:
|
case 77:
|
||||||
// M key
|
// M key
|
||||||
if (!held) {
|
if (!held) {
|
||||||
this.muted = 'toggle';
|
this.muted = !this.muted;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -145,6 +144,11 @@ const listeners = {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 76:
|
||||||
|
// L key
|
||||||
|
this.loop = !this.loop;
|
||||||
|
break;
|
||||||
|
|
||||||
/* case 73:
|
/* case 73:
|
||||||
this.setLoop('start');
|
this.setLoop('start');
|
||||||
break;
|
break;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user