diff --git a/NEWS b/NEWS
index 1579d0e0f..b85d12751 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,7 @@ ver 0.23 (not yet released)
   - snapcast: new plugin
 * tags
   - new tags "ComposerSort", "Ensemble", "Movement", "MovementNumber", and "Location"
+* split permission "player" from "control"
 * new build-time dependency: libfmt
 
 ver 0.22.11 (2021/08/24)
diff --git a/doc/user.rst b/doc/user.rst
index a3d318102..4b26ff192 100644
--- a/doc/user.rst
+++ b/doc/user.rst
@@ -639,6 +639,9 @@ By default, all clients are unauthenticated and have a full set of permissions.
      - Allows reading of the database, displaying the current playlist, and current status of :program:`MPD`.
    * - **add**
      - Allows adding songs and loading playlists.
+   * - **player**
+     - Allows any player and queue manipulation (start/pause/stop
+       playback etc.).
    * - **control**
      - Allows all other player and playlist manipulations.
    * - **admin**
diff --git a/src/Permission.cxx b/src/Permission.cxx
index f04bef042..a76feb195 100644
--- a/src/Permission.cxx
+++ b/src/Permission.cxx
@@ -41,6 +41,7 @@ static constexpr struct {
 } permission_names[] = {
 	{ "read", PERMISSION_READ },
 	{ "add", PERMISSION_ADD },
+	{ "player", PERMISSION_PLAYER },
 	{ "control", PERMISSION_CONTROL },
 	{ "admin", PERMISSION_ADMIN },
 	{ nullptr, 0 },
@@ -75,6 +76,11 @@ static unsigned parsePermissions(const char *string)
 		if (!i.empty())
 			permission |= ParsePermission(i);
 
+	/* for backwards compatiblity with MPD 0.22 and older,
+	   "control" implies "play" */
+	if (permission & PERMISSION_CONTROL)
+		permission |= PERMISSION_PLAYER;
+
 	return permission;
 }
 
diff --git a/src/Permission.hxx b/src/Permission.hxx
index cd333e748..79202cb32 100644
--- a/src/Permission.hxx
+++ b/src/Permission.hxx
@@ -29,6 +29,7 @@ static constexpr unsigned PERMISSION_READ = 1;
 static constexpr unsigned PERMISSION_ADD = 2;
 static constexpr unsigned PERMISSION_CONTROL = 4;
 static constexpr unsigned PERMISSION_ADMIN = 8;
+static constexpr unsigned PERMISSION_PLAYER = 16;
 
 int
 getPermissionFromPassword(const char *password, unsigned *permission) noexcept;
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index 2b9b07fdb..f90f696d0 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -91,21 +91,21 @@ static constexpr struct command commands[] = {
 	{ "albumart", PERMISSION_READ, 2, 2, handle_album_art },
 	{ "binarylimit", PERMISSION_NONE, 1, 1, handle_binary_limit },
 	{ "channels", PERMISSION_READ, 0, 0, handle_channels },
-	{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
-	{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
+	{ "clear", PERMISSION_PLAYER, 0, 0, handle_clear },
+	{ "clearerror", PERMISSION_PLAYER, 0, 0, handle_clearerror },
 	{ "cleartagid", PERMISSION_ADD, 1, 2, handle_cleartagid },
 	{ "close", PERMISSION_NONE, -1, -1, handle_close },
 	{ "commands", PERMISSION_NONE, 0, 0, handle_commands },
 	{ "config", PERMISSION_ADMIN, 0, 0, handle_config },
-	{ "consume", PERMISSION_CONTROL, 1, 1, handle_consume },
+	{ "consume", PERMISSION_PLAYER, 1, 1, handle_consume },
 #ifdef ENABLE_DATABASE
 	{ "count", PERMISSION_READ, 1, -1, handle_count },
 #endif
-	{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
+	{ "crossfade", PERMISSION_PLAYER, 1, 1, handle_crossfade },
 	{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
 	{ "decoders", PERMISSION_READ, 0, 0, handle_decoders },
-	{ "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
-	{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
+	{ "delete", PERMISSION_PLAYER, 1, 1, handle_delete },
+	{ "deleteid", PERMISSION_PLAYER, 1, 1, handle_deleteid },
 	{ "delpartition", PERMISSION_ADMIN, 1, 1, handle_delpartition },
 	{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
 	{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
@@ -137,25 +137,25 @@ static constexpr struct command commands[] = {
 	{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
 	{ "load", PERMISSION_ADD, 1, 2, handle_load },
 	{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
-	{ "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
-	{ "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
+	{ "mixrampdb", PERMISSION_PLAYER, 1, 1, handle_mixrampdb },
+	{ "mixrampdelay", PERMISSION_PLAYER, 1, 1, handle_mixrampdelay },
 #ifdef ENABLE_DATABASE
 	{ "mount", PERMISSION_ADMIN, 2, 2, handle_mount },
 #endif
-	{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
-	{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
+	{ "move", PERMISSION_PLAYER, 2, 2, handle_move },
+	{ "moveid", PERMISSION_PLAYER, 2, 2, handle_moveid },
 	{ "moveoutput", PERMISSION_ADMIN, 1, 1, handle_moveoutput },
 	{ "newpartition", PERMISSION_ADMIN, 1, 1, handle_newpartition },
-	{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
+	{ "next", PERMISSION_PLAYER, 0, 0, handle_next },
 	{ "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands },
 	{ "outputs", PERMISSION_READ, 0, 0, handle_devices },
 	{ "outputset", PERMISSION_ADMIN, 3, 3, handle_outputset },
 	{ "partition", PERMISSION_READ, 1, 1, handle_partition },
 	{ "password", PERMISSION_NONE, 1, 1, handle_password },
-	{ "pause", PERMISSION_CONTROL, 0, 1, handle_pause },
+	{ "pause", PERMISSION_PLAYER, 0, 1, handle_pause },
 	{ "ping", PERMISSION_NONE, 0, 0, handle_ping },
-	{ "play", PERMISSION_CONTROL, 0, 1, handle_play },
-	{ "playid", PERMISSION_CONTROL, 0, 1, handle_playid },
+	{ "play", PERMISSION_PLAYER, 0, 1, handle_play },
+	{ "playid", PERMISSION_PLAYER, 0, 1, handle_playid },
 	{ "playlist", PERMISSION_READ, 0, 0, handle_playlist },
 	{ "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd },
 	{ "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
@@ -167,17 +167,17 @@ static constexpr struct command commands[] = {
 	{ "playlistsearch", PERMISSION_READ, 1, -1, handle_playlistsearch },
 	{ "plchanges", PERMISSION_READ, 1, 2, handle_plchanges },
 	{ "plchangesposid", PERMISSION_READ, 1, 2, handle_plchangesposid },
-	{ "previous", PERMISSION_CONTROL, 0, 0, handle_previous },
-	{ "prio", PERMISSION_CONTROL, 2, -1, handle_prio },
-	{ "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid },
-	{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
+	{ "previous", PERMISSION_PLAYER, 0, 0, handle_previous },
+	{ "prio", PERMISSION_PLAYER, 2, -1, handle_prio },
+	{ "prioid", PERMISSION_PLAYER, 2, -1, handle_prioid },
+	{ "random", PERMISSION_PLAYER, 1, 1, handle_random },
 	{ "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid },
 	{ "readcomments", PERMISSION_READ, 1, 1, handle_read_comments },
 	{ "readmessages", PERMISSION_READ, 0, 0, handle_read_messages },
 	{ "readpicture", PERMISSION_READ, 2, 2, handle_read_picture },
 	{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
-	{ "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
-	{ "replay_gain_mode", PERMISSION_CONTROL, 1, 1,
+	{ "repeat", PERMISSION_PLAYER, 1, 1, handle_repeat },
+	{ "replay_gain_mode", PERMISSION_PLAYER, 1, 1,
 	  handle_replay_gain_mode },
 	{ "replay_gain_status", PERMISSION_READ, 0, 0,
 	  handle_replay_gain_status },
@@ -189,22 +189,22 @@ static constexpr struct command commands[] = {
 	{ "searchadd", PERMISSION_ADD, 1, -1, handle_searchadd },
 	{ "searchaddpl", PERMISSION_CONTROL, 2, -1, handle_searchaddpl },
 #endif
-	{ "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
-	{ "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur },
-	{ "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
+	{ "seek", PERMISSION_PLAYER, 2, 2, handle_seek },
+	{ "seekcur", PERMISSION_PLAYER, 1, 1, handle_seekcur },
+	{ "seekid", PERMISSION_PLAYER, 2, 2, handle_seekid },
 	{ "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message },
-	{ "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol },
-	{ "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle },
-	{ "single", PERMISSION_CONTROL, 1, 1, handle_single },
+	{ "setvol", PERMISSION_PLAYER, 1, 1, handle_setvol },
+	{ "shuffle", PERMISSION_PLAYER, 0, 1, handle_shuffle },
+	{ "single", PERMISSION_PLAYER, 1, 1, handle_single },
 	{ "stats", PERMISSION_READ, 0, 0, handle_stats },
 	{ "status", PERMISSION_READ, 0, 0, handle_status },
 #ifdef ENABLE_SQLITE
 	{ "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker },
 #endif
-	{ "stop", PERMISSION_CONTROL, 0, 0, handle_stop },
+	{ "stop", PERMISSION_PLAYER, 0, 0, handle_stop },
 	{ "subscribe", PERMISSION_READ, 1, 1, handle_subscribe },
-	{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
-	{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
+	{ "swap", PERMISSION_PLAYER, 2, 2, handle_swap },
+	{ "swapid", PERMISSION_PLAYER, 2, 2, handle_swapid },
 	{ "tagtypes", PERMISSION_NONE, 0, -1, handle_tagtypes },
 	{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
 #ifdef ENABLE_DATABASE
@@ -213,7 +213,7 @@ static constexpr struct command commands[] = {
 	{ "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe },
 	{ "update", PERMISSION_CONTROL, 0, 1, handle_update },
 	{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
-	{ "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
+	{ "volume", PERMISSION_PLAYER, 1, 1, handle_volume },
 };
 
 static constexpr unsigned num_commands = std::size(commands);