Initial merge of the BMediaClient branch
* The BMediaClient is an higher level API to the media_kit. It corresponds to what the layout API was for the interface_kit. The main idea is to allow the developer concentrate only on higher level details and avoiding handle with the tricky parts of the media_kit. At the same time the general purpose node that is implemented inside would allow implementing the best techniques around thus at the same time reducing code duplication and increasing efficiency. * BMediaClient is WIP, this is the initial merge of the branch. The initial development stone was set long time ago and walked through various design/implementation phases.
This commit is contained in:
Executable
+257
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright 2015, Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
#ifndef _MEDIA_CLIENT_H
|
||||
#define _MEDIA_CLIENT_H
|
||||
|
||||
#include <ObjectList.h>
|
||||
#include <Buffer.h>
|
||||
|
||||
#include <MediaAddOn.h>
|
||||
#include <MediaConnection.h>
|
||||
#include <MediaDefs.h>
|
||||
#include <MediaNode.h>
|
||||
|
||||
#include "MediaClientNode.h"
|
||||
|
||||
|
||||
namespace BPrivate { namespace media {
|
||||
|
||||
// The node can receive media data.
|
||||
#define B_MEDIA_RECORDER 0x000000001
|
||||
// The node can send media data to another node.
|
||||
#define B_MEDIA_PLAYER 0x000000002
|
||||
// The node specify a control GUI which can be used to configure it.
|
||||
#define B_MEDIA_CONTROLLABLE 0x000000004
|
||||
|
||||
// TODO: Add file interface
|
||||
// TODO: Offline mode is still missing
|
||||
|
||||
// BMediaClient is a general purpose class allowing to create any kind
|
||||
// of media_node. It automatically manage the expected behavior under
|
||||
// different run modes, and allow to specify the different capabilities
|
||||
// which you may want. It work with a central loop allowing to handle,
|
||||
// calling the right callbacks automatically events at a certain
|
||||
// performance_time.
|
||||
class BMediaClient {
|
||||
public:
|
||||
enum notification {
|
||||
B_WILL_START = 1, // performance_time
|
||||
B_WILL_STOP, // performance_time immediate
|
||||
B_WILL_SEEK, // performance_time media_time
|
||||
B_WILL_TIMEWARP, // real_time performance_time
|
||||
|
||||
B_FORMAT_SUGGESTION, // media_type type, int32 quality,
|
||||
// media_format* format
|
||||
};
|
||||
|
||||
typedef void (*notify_hook)(void* cookie,
|
||||
notification what,
|
||||
...);
|
||||
|
||||
// TODO: Should allow BControllable capabilities
|
||||
BMediaClient(const char* name,
|
||||
media_type type
|
||||
= B_MEDIA_UNKNOWN_TYPE,
|
||||
uint64
|
||||
capabilities = B_MEDIA_PLAYER
|
||||
& B_MEDIA_RECORDER);
|
||||
|
||||
virtual ~BMediaClient();
|
||||
|
||||
status_t InitCheck() const;
|
||||
|
||||
// Return the capabilities of this BMediaClient instance.
|
||||
uint64 Capabilities() const;
|
||||
media_type Type() const;
|
||||
|
||||
// To connect pass the BMediaConnection to this class or to another BMediaClient,
|
||||
// also in another team the connection object will be valid.
|
||||
// When those functions return, the BMediaConnection is added to the
|
||||
// list and is visible to other nodes as not connected.
|
||||
|
||||
// This is supplied to support generic connections not related
|
||||
// to a certain destination or source node, however for various ambiguity
|
||||
// reasons we want the BMediaConnection to be declared as input or output.
|
||||
// You can pass the object returned by this function to another
|
||||
// BMediaClient::BeginConnection() and then Connect(), so that it
|
||||
// will automatically connect to this node.
|
||||
virtual BMediaConnection* BeginConnection(media_connection_kind kind);
|
||||
|
||||
// Those are used if you want to connect to a precise input/output of
|
||||
// another node, the connection returned represents a remote input/output.
|
||||
virtual BMediaConnection* BeginConnection(const media_input& input);
|
||||
virtual BMediaConnection* BeginConnection(const media_output& output);
|
||||
|
||||
// This is used when we want to connect us with another BMediaClient, the
|
||||
// returned connection is the one you will use to call the Connect method
|
||||
// and will represents it by the point of view of this node. Don't try to
|
||||
// use directly connections built by another BMediaClient, it will fail,
|
||||
// always pass between this function to get a local connection from the point
|
||||
// of view of *this* node and then call Connect().
|
||||
virtual BMediaConnection* BeginConnection(BMediaConnection* connection);
|
||||
|
||||
// Bind internally two connections of the same BMediaClient, so that the
|
||||
// input will be automatically forwarded to the output just after the
|
||||
// ProcessFunc is called. The buffer is automatically recycled too.
|
||||
// Beware that the binding operation is valid only for local connections
|
||||
// which belong to this node, otherwise return B_ERROR.
|
||||
virtual status_t Bind(BMediaConnection* input,
|
||||
BMediaConnection* output);
|
||||
|
||||
virtual status_t Unbind(BMediaConnection* input,
|
||||
BMediaConnection* output);
|
||||
|
||||
// If the user want a particular format for a connection it should
|
||||
// use BMediaConnection::SetAcceptedFormat(), if it's not specified
|
||||
// BMediaClient::Format() will be used, in case both aren't specified
|
||||
// an error is returned. The connection should always belong to this
|
||||
// node, in any other case it will return an error.
|
||||
virtual status_t Connect(BMediaConnection* ourConnection,
|
||||
BMediaConnection* theirConnection);
|
||||
|
||||
virtual status_t Connect(BMediaConnection* ourConnection,
|
||||
const dormant_node_info& dormantInfo);
|
||||
|
||||
virtual status_t Connect(BMediaConnection* ourConnection,
|
||||
const media_node& node);
|
||||
|
||||
// Disconnect any connection belonging to this object, to disconnect
|
||||
// a single connection use BMediaConnection::Disconnect().
|
||||
virtual status_t Disconnect();
|
||||
|
||||
int32 CountConnections() const;
|
||||
int32 CountInputs() const;
|
||||
int32 CountOutputs() const;
|
||||
BMediaConnection* InputAt(int32 index) const;
|
||||
BMediaConnection* OutputAt(int32 index) const;
|
||||
|
||||
BMediaConnection* FindConnection(
|
||||
const media_destination& dest) const;
|
||||
BMediaConnection* FindConnection(
|
||||
const media_source& source) const;
|
||||
|
||||
bool IsRunning() const;
|
||||
|
||||
virtual status_t Start(bool force = false);
|
||||
virtual status_t Stop(bool force = false);
|
||||
status_t Seek(bigtime_t mediaTime,
|
||||
bigtime_t performanceTime);
|
||||
status_t Roll(bigtime_t start, bigtime_t stop,
|
||||
bigtime_t seek);
|
||||
status_t Preroll();
|
||||
status_t SyncTo(bigtime_t performanceTime,
|
||||
bigtime_t timeout = -1);
|
||||
|
||||
// It will be B_INCREASE_LATENCY by default
|
||||
BMediaNode::run_mode RunMode() const;
|
||||
status_t SetRunMode(BMediaNode::run_mode mode);
|
||||
status_t SetTimeSource(media_node timesource);
|
||||
|
||||
// Specify a latency range to allow the node behave correctly.
|
||||
// Ideally the minimum latency should be the algorithmic latency you expect
|
||||
// from the node and will be used as starting point. The max latency is the
|
||||
// maximum acceptable by you, over that point the node will adjust it's
|
||||
// performance time to recover if a big delay happen.
|
||||
void SetLatencyRange(bigtime_t min,
|
||||
bigtime_t max);
|
||||
|
||||
void GetLatencyRange(bigtime_t* min,
|
||||
bigtime_t* max) const;
|
||||
|
||||
// When in B_OFFLINE it returns OfflineTime().
|
||||
bigtime_t OfflineTime() const;
|
||||
// Return the current performance time handled by the object
|
||||
// when run_mode != B_OFFLINE.
|
||||
bigtime_t PerformanceTime() const;
|
||||
|
||||
// When a connection is not binded with another, it's your job to send
|
||||
// the buffer to the connection you want. You might want
|
||||
// to ovverride it so that you can track something, in this case
|
||||
// be sure to call the base version.
|
||||
// Automatically recycle the BBuffer.
|
||||
virtual status_t SendBuffer(BMediaConnection* connection,
|
||||
BBuffer* buffer);
|
||||
|
||||
// This is supplied to support using this class in a BMediaAddOn.
|
||||
// Default version just return NULL.
|
||||
virtual BMediaAddOn* AddOn(int32* id) const;
|
||||
|
||||
void SetNotificationHook(notify_hook notifyHook = NULL,
|
||||
void* cookie = NULL);
|
||||
|
||||
protected:
|
||||
virtual void BufferReceived(BMediaConnection* connection,
|
||||
BBuffer* buffer);
|
||||
|
||||
// This is used when the user want to override the BeginConnection
|
||||
// mechanism, for example to supply your BMediaConnection derived
|
||||
// class. Take ownership of the object.
|
||||
virtual void AddConnection(BMediaConnection* connection);
|
||||
|
||||
// Called from BMediaConnection
|
||||
status_t DisconnectConnection(BMediaConnection* conn);
|
||||
status_t ResetConnection(BMediaConnection* conn);
|
||||
status_t ReleaseConnection(BMediaConnection* conn);
|
||||
private:
|
||||
|
||||
void _Init();
|
||||
void _Deinit();
|
||||
|
||||
status_t _TranslateConnection(BMediaConnection* dest,
|
||||
BMediaConnection* source);
|
||||
|
||||
status_t _Connect(BMediaConnection* conn,
|
||||
media_node node);
|
||||
status_t _ConnectInput(BMediaConnection* output,
|
||||
BMediaConnection* input);
|
||||
status_t _ConnectOutput(BMediaConnection* input,
|
||||
BMediaConnection* output);
|
||||
|
||||
status_t fInitErr;
|
||||
|
||||
uint64 fCapabilities;
|
||||
|
||||
bool fRunning;
|
||||
BMediaClientNode* fNode;
|
||||
|
||||
bigtime_t fPerformanceTime;
|
||||
bigtime_t fOfflineTime;
|
||||
|
||||
bigtime_t fMinLatency;
|
||||
bigtime_t fMaxLatency;
|
||||
|
||||
notify_hook fNotifyHook;
|
||||
|
||||
void* fNotifyCookie;
|
||||
|
||||
BObjectList<BMediaConnection> fInputs;
|
||||
BObjectList<BMediaConnection> fOutputs;
|
||||
|
||||
virtual void _ReservedMediaClient0();
|
||||
virtual void _ReservedMediaClient1();
|
||||
virtual void _ReservedMediaClient2();
|
||||
virtual void _ReservedMediaClient3();
|
||||
virtual void _ReservedMediaClient4();
|
||||
virtual void _ReservedMediaClient5();
|
||||
virtual void _ReservedMediaClient6();
|
||||
virtual void _ReservedMediaClient7();
|
||||
virtual void _ReservedMediaClient8();
|
||||
virtual void _ReservedMediaClient9();
|
||||
virtual void _ReservedMediaClient10();
|
||||
uint32 fPadding[64];
|
||||
|
||||
friend class BMediaClientNode;
|
||||
friend class BMediaConnection;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using namespace BPrivate::media;
|
||||
|
||||
#endif
|
||||
Executable
+151
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2015, Dario Casalinuovo. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
#ifndef _MEDIA_CLIENT_NODE_H
|
||||
#define _MEDIA_CLIENT_NODE_H
|
||||
|
||||
|
||||
#include <BufferConsumer.h>
|
||||
#include <BufferProducer.h>
|
||||
#include <Controllable.h>
|
||||
#include <MediaDefs.h>
|
||||
#include <MediaEventLooper.h>
|
||||
|
||||
|
||||
namespace BPrivate { namespace media {
|
||||
|
||||
class BMediaClient;
|
||||
class BMediaConnection;
|
||||
|
||||
class BMediaClientNode : public BBufferConsumer, public BBufferProducer,
|
||||
public BMediaEventLooper {
|
||||
public:
|
||||
BMediaClientNode(const char* name,
|
||||
BMediaClient* owner,
|
||||
media_type type
|
||||
= B_MEDIA_UNKNOWN_TYPE);
|
||||
|
||||
// Various useful stuff
|
||||
|
||||
status_t SendBuffer(BBuffer* buffer, BMediaConnection* conn);
|
||||
|
||||
protected:
|
||||
|
||||
virtual BMediaAddOn* AddOn(int32* id) const;
|
||||
|
||||
virtual void NodeRegistered();
|
||||
|
||||
virtual void SetRunMode(run_mode mode);
|
||||
|
||||
virtual void Start(bigtime_t performanceTime);
|
||||
|
||||
virtual void Stop(bigtime_t performanceTime,
|
||||
bool immediate);
|
||||
|
||||
virtual void Seek(bigtime_t mediaTime,
|
||||
bigtime_t performanceTime);
|
||||
|
||||
virtual void TimeWarp(bigtime_t realTime,
|
||||
bigtime_t performanceTime);
|
||||
|
||||
virtual status_t HandleMessage(int32 message,
|
||||
const void* data,
|
||||
size_t size);
|
||||
|
||||
// BBufferConsumer
|
||||
|
||||
virtual status_t AcceptFormat(const media_destination& dest,
|
||||
media_format* format);
|
||||
|
||||
virtual status_t GetNextInput(int32* cookie,
|
||||
media_input* input);
|
||||
|
||||
virtual void DisposeInputCookie(int32 cookie);
|
||||
|
||||
virtual void BufferReceived(BBuffer* buffer);
|
||||
|
||||
virtual status_t GetLatencyFor(const media_destination& dest,
|
||||
bigtime_t* latency,
|
||||
media_node_id* timesource);
|
||||
|
||||
virtual status_t Connected(const media_source& source,
|
||||
const media_destination& where,
|
||||
const media_format& format,
|
||||
media_input* outInput);
|
||||
|
||||
virtual void Disconnected(const media_source& source,
|
||||
const media_destination& where);
|
||||
|
||||
virtual status_t FormatChanged(const media_source& source,
|
||||
const media_destination& consumer,
|
||||
int32 tag,
|
||||
const media_format& format);
|
||||
|
||||
// BBufferProducer
|
||||
|
||||
virtual status_t FormatSuggestionRequested(media_type type,
|
||||
int32 quality, media_format* format);
|
||||
virtual status_t FormatProposal(const media_source& source,
|
||||
media_format *format);
|
||||
virtual status_t FormatChangeRequested(const media_source& source,
|
||||
const media_destination& dest,
|
||||
media_format *format,
|
||||
int32* _deprecated_);
|
||||
virtual void LateNoticeReceived(const media_source& source,
|
||||
bigtime_t late, bigtime_t when);
|
||||
virtual status_t GetNextOutput(int32 *cookie, media_output *output);
|
||||
virtual status_t DisposeOutputCookie(int32 cookie);
|
||||
virtual status_t SetBufferGroup(const media_source& source,
|
||||
BBufferGroup *group);
|
||||
virtual status_t PrepareToConnect(const media_source& source,
|
||||
const media_destination& dest,
|
||||
media_format *format,
|
||||
media_source *out_source,
|
||||
char *name);
|
||||
virtual void Connect(status_t status,
|
||||
const media_source& source,
|
||||
const media_destination& dest,
|
||||
const media_format &format,
|
||||
char* name);
|
||||
virtual void Disconnect(const media_source& source,
|
||||
const media_destination& dest);
|
||||
virtual void EnableOutput(const media_source& source,
|
||||
bool enabled, int32* _deprecated_);
|
||||
virtual status_t GetLatency(bigtime_t *outLatency);
|
||||
virtual void LatencyChanged( const media_source& source,
|
||||
const media_destination& dest,
|
||||
bigtime_t latency, uint32 flags);
|
||||
|
||||
void ProducerDataStatus(const media_destination& dest,
|
||||
int32 status, bigtime_t when);
|
||||
protected:
|
||||
virtual void HandleEvent(const media_timed_event *event,
|
||||
bigtime_t late,
|
||||
bool realTimeEvent=false);
|
||||
|
||||
virtual ~BMediaClientNode();
|
||||
|
||||
private:
|
||||
void _HandleBuffer(BBuffer* buffer);
|
||||
void _ProduceNewBuffer(const media_timed_event* event,
|
||||
bigtime_t late);
|
||||
|
||||
void _HandleStart(bigtime_t performanceTime);
|
||||
void _HandleStop(bigtime_t performanceTime,
|
||||
bool immediate);
|
||||
void _HandleSeek(bigtime_t mediaTime,
|
||||
bigtime_t performanceTime);
|
||||
void _HandleTimeWarp(bigtime_t realTime,
|
||||
bigtime_t performanceTime);
|
||||
|
||||
BMediaClient* fOwner;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
using namespace BPrivate::media;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2015, Haiku, Inc. All rights reserved.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
#ifndef _MEDIA_CONNECTION_H
|
||||
#define _MEDIA_CONNECTION_H
|
||||
|
||||
#include <BufferGroup.h>
|
||||
#include <MediaDefs.h>
|
||||
|
||||
#include "MediaClientNode.h"
|
||||
|
||||
|
||||
namespace BPrivate { namespace media {
|
||||
|
||||
|
||||
enum media_connection_kind {
|
||||
B_MEDIA_INPUT = 0,
|
||||
B_MEDIA_OUTPUT = 1
|
||||
};
|
||||
|
||||
|
||||
class BMediaClient;
|
||||
|
||||
// The BMediaConnection class is the swiss knife of BMediaClient.
|
||||
// It represents a connection between two nodes and allow to create complex
|
||||
// nodes without dealing with the unneeded complexity. Two local connections,
|
||||
// can be binded, this means that when you will receive a buffer A as input,
|
||||
// the Process function will be called so that you can process the BBuffer,
|
||||
// and once the function returns the output will be automatically forwarded
|
||||
// to the connection B.
|
||||
// If you don't bind a connection, you have to call yourself the
|
||||
// BMediaClient::SendBuffer() method or call BMediaClient::Recycle to
|
||||
// recycle the buffer when you don't want to do anything further.
|
||||
class BMediaConnection {
|
||||
public:
|
||||
enum notification {
|
||||
B_CONNECTED = 1,
|
||||
B_DISCONNECTED,
|
||||
|
||||
B_PREPARE_TO_CONNECT, // media_format* format, media_source* source,
|
||||
// char* name
|
||||
B_CONNECT,
|
||||
B_DISCONNECT,
|
||||
|
||||
B_FORMAT_PROPOSAL, // media_format* format
|
||||
|
||||
B_ASK_FORMAT_CHANGE,
|
||||
B_FORMAT_CHANGED,
|
||||
B_ASK_TIMER
|
||||
};
|
||||
|
||||
// This function is called when it has come the moment to handle a buffer.
|
||||
typedef void (*process_hook)(BMediaConnection* connection,
|
||||
BBuffer* buffer);
|
||||
|
||||
// Used to notify or inquire the client about what to do when certain
|
||||
// events happen.
|
||||
typedef status_t (*notify_hook)(notification what,
|
||||
BMediaConnection* connection,
|
||||
...);
|
||||
|
||||
virtual ~BMediaConnection();
|
||||
|
||||
// TODO: while most of the objects for both kinds are common
|
||||
// it would be worthwile to have a private implementation
|
||||
// so that we can better model the differences and avoid
|
||||
// problems.
|
||||
bool IsOutput() const;
|
||||
bool IsInput() const;
|
||||
|
||||
const media_destination& Destination() const;
|
||||
const media_source& Source() const;
|
||||
|
||||
bool HasBinding() const;
|
||||
BMediaConnection* Binding() const;
|
||||
|
||||
// This allow to specify a format that will be used while
|
||||
// connecting to another node. See BMediaClient::SetFormat.
|
||||
void SetAcceptedFormat(
|
||||
const media_format& format);
|
||||
const media_format& AcceptedFormat() const;
|
||||
|
||||
// Represents the buffer size used by the media_node
|
||||
void SetBufferSize(size_t size);
|
||||
size_t BufferSize() const;
|
||||
|
||||
// Represents the duration of one buffer
|
||||
void SetBufferDuration(bigtime_t duration);
|
||||
bigtime_t BufferDuration() const;
|
||||
|
||||
bool IsConnected() const;
|
||||
|
||||
void SetOutputEnabled(bool enabled);
|
||||
bool IsOutputEnabled() const;
|
||||
|
||||
void SetCookie(void* cookie);
|
||||
void* Cookie() const;
|
||||
|
||||
// Disconnect this connection.
|
||||
status_t Disconnect();
|
||||
|
||||
// TODO: We really need a Reset()?
|
||||
// When you reset a connection it can be reused as it was brand new.
|
||||
status_t Reset();
|
||||
|
||||
// Once you are done with this connection you release it, it automatically
|
||||
// remove the object from the BMediaClient and free all used resources.
|
||||
// This will make the connection to disappear completely, so if you
|
||||
// want to preserve it for future connections just Disconnect() and
|
||||
// Reset() it.
|
||||
status_t Release();
|
||||
|
||||
// Use this to set your callbacks.
|
||||
void SetHooks(process_hook processHook = NULL,
|
||||
notify_hook notifyHook = NULL,
|
||||
void* cookie = NULL);
|
||||
|
||||
protected:
|
||||
BMediaConnection(BMediaClient* owner,
|
||||
media_connection_kind kind);
|
||||
BMediaConnection(BMediaClient* owner,
|
||||
const media_output& output);
|
||||
BMediaConnection(BMediaClient* owner,
|
||||
const media_input& input);
|
||||
|
||||
// TODO: All notifications should be done into private callbacks like this.
|
||||
void ConnectedCallback(const media_source& source,
|
||||
const media_format& format);
|
||||
void DisconnectedCallback(const media_source& source);
|
||||
|
||||
void ConnectCallback(const media_destination& source);
|
||||
void DisconnectCallback(const media_destination& source);
|
||||
|
||||
private:
|
||||
void BuildMediaOutput(media_output* output) const;
|
||||
void BuildMediaInput(media_input* output) const;
|
||||
|
||||
void _Init();
|
||||
|
||||
bool fConnected;
|
||||
bool fOutputEnabled;
|
||||
|
||||
media_connection_kind fKind;
|
||||
BMediaClient* fOwner;
|
||||
media_node fOwnerNode;
|
||||
team_id fOwnerTeam;
|
||||
|
||||
// A connection might be binded so that it will automatically
|
||||
// forward or receive the data from/to a local BMediaConnection,
|
||||
// see BMediaClient::Bind.
|
||||
BMediaConnection* fBind;
|
||||
|
||||
process_hook fProcessHook;
|
||||
notify_hook fNotifyHook;
|
||||
void* fBufferCookie;
|
||||
|
||||
media_source fSource;
|
||||
media_destination fDestination;
|
||||
|
||||
// This represents the node at other end of connection.
|
||||
media_node fRemoteNode;
|
||||
|
||||
media_format fFormat;
|
||||
size_t fBufferSize;
|
||||
bigtime_t fBufferDuration;
|
||||
|
||||
BBufferGroup* fBufferGroup;
|
||||
|
||||
virtual void _ReservedMediaConnection0();
|
||||
virtual void _ReservedMediaConnection1();
|
||||
virtual void _ReservedMediaConnection2();
|
||||
virtual void _ReservedMediaConnection3();
|
||||
virtual void _ReservedMediaConnection4();
|
||||
virtual void _ReservedMediaConnection5();
|
||||
virtual void _ReservedMediaConnection6();
|
||||
virtual void _ReservedMediaConnection7();
|
||||
virtual void _ReservedMediaConnection8();
|
||||
virtual void _ReservedMediaConnection9();
|
||||
virtual void _ReservedMediaConnection10();
|
||||
uint32 fPadding[64];
|
||||
|
||||
friend class BMediaClient;
|
||||
friend class BMediaClientNode;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using namespace BPrivate::media;
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user